Enhanced Database Query Expressions for Laravel. Provides expressions with bindings and expressions that can be used as column values in eloquent
angel-source-labs/laravel-expressions is a Laravel package for enhanced database query expressions for laravel. provides expressions with bindings and expressions that can be used as column values in eloquent.
It currently has 1 GitHub stars and 12.816 downloads on Packagist (latest version v1.3.1).
Install it with composer require angel-source-labs/laravel-expressions.
Discover more Laravel packages by angel-source-labs
or browse all Laravel packages to compare alternatives.
Last updated
An Expression is a string of raw sql that can be used in Laravel Query Builder statements.
The Laravel documentation presents the concept of Raw Expressions as
raw SQL strings that can be created via the DB::raw facade or by using any of the raw methods:
selectRawwhereRaw / orWhereRawhavingRaw / orHavingRaworderByRawgroupByRawLaravel represents these expressions as an Expression object that can be created using the DB::raw method.
This package enhances Expressions with the following features
Expression subclasses that are semantically meaningfulExpression by implementing the IsExpression interfaceExpressionGrammar: An Expression can produce the appropriate different grammar for each database by using the ExpressionGrammar helper classThe following Laravel versions are supported:
Install the package with composer
composer require angel-source-labs/laravel-expressions
artisan expressions:doctorThis package injects new database connection, grammar, and query builder classes, so it potentially conflicts with other packages that inject or override database connections, grammar, or query builder classes.
To test that the installation is working and is not experiencing conflicts from other packages, this package includes an artisan expressions:doctor command
that will run tests to verify that the database connections are resolving properly and expressions are building properly.
To run the doctor, type php artisan expressions:doctor at the command line at the base of your Laravel project.
The following are examples of packages that possibly conflict with this package.
Create an expression by creating a new instance of AngelSourceLabs\LaravelExpressions\Database\Query\Expression\Expression.
public function testSelectRawUsingExpression()
{
$expression = new Expression("price as price_before_tax");
$sql = DB::table('orders')->selectRaw($expression)->toSql();
$this->assertEquals('select price as price_before_tax from `orders`', $sql);
}
Create an expression with bindings by creating a new instance of AngelSourceLabs\LaravelExpressions\Database\Query\Expression\Expression. The first parameter is the
raw sql expression using ? placeholders for the bindings. The second parameter is an array of binding values.
$expression = new Expression("inet_aton(?)", ["192.168.0.1"]);
DB::table('audits')->where('ip', $expression)->get();
This produces the SQL 'select * from auditswhereip = inet_aton(?)' with a PDO binding of [1 => "192.168.0.1"]
You can create reusable expressions classes with semantic meaning.
public class InetAtoN extends Expression
{
public function __construct($address)
{
parent::__construct("inet_aton(?)", $address);
}
}
DB::table('audits')->where('ip', new InetAtoN("192.168.0.1"))->get();
Expressions can be stored in Eloquent model attributes and will be used in insert and update statements.
public class Point extends Expression
{
public function __construct($lat, $lng)
{
parent::__construct("ST_GeomFromText(?, ?)", [$lng, $lat]);
}
}
$model->point = new Point(44.9561062,-93.1041534);
$model->save();
results in the following insert or update statement depending on whether the record is new or already existing:
-- example insert statement result
insert into "test_models" ("point") values (ST_GeomFromText(?, ?)) returning "id";
-- example update statement result
update "test_models" set "point" = ST_GeomFromText(?, ?) where "id" = ?;
IsExpression interface and ProvidesExpression traitWhen building domain classes, a class may already extend from another class and may not always be able to extend from
Expression.
You can turn any class into an expression by implementing the IsExpression interface.
You can also use the trait ProvidesExpression to add the default implementation to your class.
class ClassIsExpression implements IsExpression
{
use ProvidesExpression;
}
function testSelectRawUsingExpression()
{
$expression = new ClassIsExpression("price as price_before_tax");
$sql = DB::table('orders')->selectRaw($expression)->toSql();
$this->assertEquals('select price as price_before_tax from `orders`', $sql);
}
In fact the Expression class is implemented using the IsExpression interface and ProvidesExpression trait.
use Illuminate\Database\Query\Expression as BaseExpression;
class Expression extends BaseExpression implements IsExpression
{
use ProvidesExpression;
}
ExpressionGrammar: Provide expressions with grammar differences by databaseSometimes SQL expressions need to provide different grammar for different databases and for different versions of databases.
This package provides an ExpressionGrammar class that will produce the appropriate expression for the database and version in use.
For example, when working with ST_GeomFromText() between MySQL 8.0 vs MySQL 5.7 and Postgres, the order of latitude and longitude is different,
and when switching between databases you might want your code base to work the same without changes. MySQL 8.0 provides an option
for ST_GeomFromText() to change the axis order. So while the grammar for Postgres will look like ST_GeomFromText(?, ?),
the grammar for MySql 8.0 will look like ST_GeomFromText(?, ?, 'axis-order=long-lat').
Creating an Expression with an ExpressionGrammar to support these three different grammars would look like this:
$grammar = ExpressionGrammar::make()
->mySql("ST_GeomFromText(?, ?)")
->mySql("ST_GeomFromText(?, ?, 'axis-order=long-lat')", "8.0")
->postgres("ST_GeomFromText(?, ?)");
$expression = new Expression($grammar, [$lon, $lat]);
This will resolve to the following expressions for the specified databases and versions:
| database | version | result | |----------|---------|--------| | MySQL | default | ST_GeomFromText(?, ?) | | MySQL | 8.0 and higher | ST_GeomFromText(?, ?, 'axis-order=long-lat') | | Postgres | default | ST_GeomFromText(?, ?) |
The ExpressionGrammar class provides a fluent interface for adding grammar expressions and has methods for each built-in Laravel driver as well
as a generic grammar method that allows specifying a driver string for other databases.
ExpressionGrammar::make()Creates a new Grammar instance and provides a fluent interface for adding grammar expressions.
ExpressionGrammar->mySql($string, $version (optional))Add an expression for MySQL grammar.
ExpressionGrammar->postgres($string, $version (optional))Add an expression for Postgres grammar.
ExpressionGrammar->sqLite($string, $version (optional))Add an expression for SQLite grammar.
ExpressionGrammar->sqlServer($string, $version (optional))Add an expression for SqlServer grammar.
ExpressionGrammar->grammar($driver, $string, $version (optional))Add an expression for grammar for other database drivers. $driver should match the driver string used by the Laravel query builder driver.
For example $grammar->postgres("ST_GeomFromText(?, ?)") is equivalent to $grammar->grammar("pgsql", "ST_GeomFromText(?, ?)").
The $version parameter is optional. When not specified, the grammar applies as the default. When specified, the grammar applies to the specified version of the database or greater.
ExpressionGrammar will throw a GrammarNotDefinedForDatabaseException if the Query Builder attempts to resolve an Expression for a Grammar that has not been defined for that database driver.
Revisiting the Point example from above using the ExpressionGrammar class to create appropriate grammar for MySql 5.7, MySql 8.0, and Postgres:
public class Point extends Expression
{
public function __construct($lat, $lng)
{
parent::__construct(ExpressionGrammar::make()
->mySql("ST_GeomFromText(?, ?)")
->mySql("ST_GeomFromText(?, ?, 'axis-order=long-lat')", "8.0")
->postgres("ST_GeomFromText(?, ?)"),
[$lng, $lat]);
}
}
$model->point = new Point(44.9561062,-93.1041534);
$model->save();
which will evaluate as an expression and result in the following SQL
-- example insert statement result
insert into "test_models" ("point") values (ST_GeomFromText(?, ?)) returning "id"; # MySQL 5.7, postgis
insert into "test_models" ("point") values (ST_GeomFromText(?, ?, 'axis-order=long-lat')) returning "id"; # MySQL 8.0 and greater
-- example update statement result
update "test_models" set "point" = ST_GeomFromText(?, ?) where "id" = ?; # MySQL 5.7, postgis
update "test_models" set "point" = ST_GeomFromText(?, ?, 'axis-order=long-lat') where "id" = ?; # MySQL 8.0 and greater
selectExample:
$expression = new Expression("price * ? as price_with_tax", [1.0825]);
DB::table('orders')->select($expression)->get();
result:
select price * ? as price_with_tax from `orders`; # bindings = [1 => 1.0825]
selectRawExample 1:
$expression = new Expression("price * ? as price_with_tax", [1.0825]);
DB::table('orders')->selectRaw($expression)->get();
result:
select price * ? as price_with_tax from `orders`; # bindings = [1 => 1.0825]
Example 2:
$expression = new Expression("price * ? as price_with_tax, price * ? as profit", [1.0825]);
DB::table('orders')->selectRaw($expression, [.20])->get();
result:
select price * ? as price_with_tax, price * ? as profit from `orders`; # bindings = [1 => 1.0825, 2 => 0.20]
whereRaw / orWhereRawExample 1:
$expression = new Expression('price > IF(state = "TX", ?, 100)', [200]);
DB::table('orders')->whereRaw($expression)->get();
result:
select * from `orders` where price > IF(state = "TX", ?, 100); # bindings = [1 => 200]
Example 2:
$expression = new Expression('price > IF(state = "TX", ?, ?)', [200]);
DB::table('orders')->whereRaw($expression, [100])->get();
result:
select * from `orders` where price > IF(state = "TX", ?, ?); # bindings = [1 => 200, 2 => 100]
havingRaw / orHavingRawExample 1:
$expression = new Expression('SUM(price) > ?', [2500]);
$sql = DB::table('orders')
->select('department', DB::raw('SUM(price) as total_sales'))
->groupBy('department')
->havingRaw($expression)
->get();
result:
select `department`, SUM(price) as total_sales from `orders` group by `department` having SUM(price) > ?; # bindings = [1 => 2500]
Example 2:
$expression = new Expression('SUM(price) > ? and AVG(price) > ?', [2500]);
$sql = DB::table('orders')
->select('department', DB::raw('SUM(price) as total_sales'))
->groupBy('department')
->havingRaw($expression, [100])
->get();
result:
select `department`, SUM(price) as total_sales from `orders` group by `department` having SUM(price) > ? and AVG(price) > ?; # bindings = [1 => 2500, 2 => 100]
orderByRawExample 1:
$ids = [12,23,34,45];
$expression = new Expression('field(id, ?, ?, ?, ?)', $ids);
DB::table('orders')
->whereIn('id', $ids)
->orderByRaw($expression)
->get();
result:
select * from `orders` where `id` in (?, ?, ?, ?) order by field(id, ?, ?, ?, ?); # bindings = [1 => 12, 2 => 23, 3 => 34, 4 => 45, 5 => 12, 6 => 23, 7 => 34, 8 => 45]
Example 2:
$ids = [12,23,34,45];
$expression = new Expression('field(id, ?, ?, ?, ?)', [12,23]);
DB::table('orders')
->whereIn('id', $ids)
->orderByRaw($expression,[34,45])
->get();
result:
select * from `orders` where `id` in (?, ?, ?, ?) order by field(id, ?, ?, ?, ?); # bindings = [1 => 12, 2 => 23, 3 => 34, 4 => 45, 5 => 12, 6 => 23, 7 => 34, 8 => 45]
groupByRawExample 1:
$expression = new Expression('price > ?', [100]);
DB::table('orders')
->select('department', 'price')
->groupByRaw($expression)
->get();
result:
select `department`, `price` from `orders` group by price > ?; # bindings = [1 => 100]
Example 2:
$expression = new Expression('price > ?, department > ?', [100]);
DB::table('orders')
->select('department', 'price')
->groupByRaw($expression, [1560])
->get();
result:
select `department`, `price` from `orders` group by price > ?, department > ?; # bindings = [1 => 100, 2=> 1560]
where / orWhereExample:
$expression = new Expression("inet_aton(?)", ["192.168.0.1"]);
DB::table('audits')->where('ip', $expression)->get();
result:
select * from `audits` where `ip` = inet_aton(?); # bindings = [1 => "192.168.0.1"]
$users = DB::table('users')
->where('votes', '=', $expression)
->where('age', '>', $expression)
->get();
$users = DB::table('users')->where('votes', $expression)->get();
These cases are currently not supported (or at least not tested) but likely could be added.
$users = DB::table('users')->where([
['status', '=', '1'],
['subscribed', '<>', '1'],
])->get();
$users = DB::table('users')
->where('name', '=', 'John')
->where(function ($query) {
$query->where('votes', '>', 100)
->orWhere('title', '=', 'Admin');
})
->get();
$users = DB::table('users')
->whereExists(function ($query) {
$query->select(DB::raw(1))
->from('orders')
->whereColumn('orders.user_id', 'users.id');
})
->get();
Subquery Where Clauses Case 1: Compare the results of subquery to a value:
use App\Models\User;
$users = User::where(function ($query) {
$query->select('type')
->from('membership')
->whereColumn('membership.user_id', 'users.id')
->orderByDesc('membership.start_date')
->limit(1);
}, 'Pro')->get();
Case 2: Compare a column to the results of a subquery
use App\Models\Income;
$incomes = Income::where('amount', '<', function ($query) {
$query->selectRaw('avg(i.amount)')->from('incomes as i');
})->get();
$users = DB::table('users')
->where('preferences->dining->meal', 'salad')
->get();
The following tests are run with this pacakge's ExpressionsServiceProvider loaded:
vendor/laravel/framework/tests/Databasevendor/laravel/framework/tests/Integration/Databasetests/Unit (this package)composer test
Excel Seeder for Laravel is open-sourced software licensed under the MIT license.