everon / criteria-builder
Everon Criteria Builder Component
Installs: 3 134 271
Dependents: 1
Suggesters: 0
Security: 0
Stars: 7
Watchers: 1
Forks: 2
Open Issues: 0
pkg:composer/everon/criteria-builder
Requires
- php: ^8
- everon/collection: ^3
- everon/factory: ^3
- everon/utils: ^3
Requires (Dev)
- mockery/mockery: ^1
- phpunit/phpunit: ^9
README
Library to generate complete SQL WHERE statements, with simple, fluid and intuitive interface.
Versions
- Use v1.x and v2.x with PHP 7.2+
- Use v3.x with PHP 8+
Features
- It's not a DQL
- SQL for what's important, fluid interface for boring stuff
- Automatic PDO parameter name escaping and uniqueness, custom parameters
- Fluid interface
- Easily to create multiple conditions
- Almost 20 ready to use Operators
- Easy to extend with Custom Operators
- Intuitive Interface: clear, small and simple API
- Clean code
No boring Sql
Focus on what's important
You can attach your own SQL via CriteriaBuilder->sql($sql) and have easy and flexible way of generating fast sql
queries without dealing with boring string concatenations, code duplication and vast amount of if/else, switch statements or constants
required to handle logic related to LIMIT, OFFSET or SORT statements.
All those boring parts were eliminated with CriteriaBuilderInterface.
Hammer won't do when you need a screwdriver
Putting boring stuff aside you have full control on how the SQL is constructed, which is helpful for highly complex,
complicated or very specific queries where using DQL makes things actually harder then easier.
DQL is great for everyday use, however sometimes you need to express yourself in very specific way,
and raw SQL is the best way to get you there.
Translate request into something database can understand
Easy to translate request parameters into something database can understand with
Operators, where statements and methods like setLimit, setOffset, or setOrderBy.
Useful for pagination or filtering, for example.
Clear separation between SQL, SQL PARAMETERS, and applying concepts like aggregation, sort, or limit.
Now you can focus only on what's important, the SQL part.
Very easy to use with PDO thanks to SqlPartInterface
$sth = $dbh->prepare($SqlPart->getSql()); $sth->execute($SqlPart->getParameters());
Examples
Simple Query
$CriteriaBuilder ->where('id', '=', 123)
Will be converted into:
WHERE (id = :id_843451772)
With parameters:
array(8) [ 'id_843451772' => integer 123 ]
Where, orWhere, andWhere
Each where statement creates new Container with Criteria object.
A Criteria object contains set of Criterium objects.
A Criterium is a condition.
You can append Criterium by using andWhere and orWhere methods.
Every time you use where statement a new Criteria will be created, ready for new set of conditions.
$CriteriaBuilder ->where('id', 'IN', [1,2,3]) ->orWhere('id', 'NOT IN', [4,5,6]) ->andWhere('name', '=', 'foo'); ->where('modified', 'IS', null) ->andWhere('name', '!=', null) ->orWhere('id', '=', 55);
Will be converted into:
WHERE ( id IN (:id_843451778,:id_897328169,:id_1377365551) OR id NOT IN (:id_1260952006,:id_519145813,:id_1367241593) AND name = :name_1178871152 ) AND ( modified IS NULL AND name IS NOT NULL OR id = :id_895877163 )
With parameters:
array(8) [ 'name_1178871152' => string (3) "foo" 'id_1260952006' => integer 4 'id_519145813' => integer 5 'id_1367241593' => integer 6 'id_843451778' => integer 1 'id_897328169' => integer 2 'id_1377365551' => integer 3 'id_895877163' => integer 55
To connect Criteria with OR operator use glueByOr method.
$CriteriaBuilder ->where('id', 'IN', [1,2,3]) ->orWhere('id', 'NOT IN', [4,5,6]) ->andWhere('name', '=', 'foo'); ->glueByOr() ->where('modified', 'IS', null) ->andWhere('name', '!=', null) ->orWhere('id', '=', 55);
Will be converted into:
WHERE ( id IN (:id_843451778,:id_897328169,:id_1377365551) OR id NOT IN (:id_1260952006,:id_519145813,:id_1367241593) AND name = :name_1178871152 ) OR ( modified IS NULL AND name IS NOT NULL OR id = :id_895877163 )
RAW SQL
RAW SQL is easy to implement with whereRaw methods.
$CriteriaBuilder ->whereRaw('foo + bar') ->andWhereRaw('1=1') ->orWhereRaw('foo::bar()');
Will be converted into:
WHERE (foo + bar AND 1=1 OR foo::bar())
Group By
Group By is easily usable with setGroupBy method
$CriteriaBuilder ->where('name', '!=', 'foo') ->andWhere('id', '=', 123) ->setGroupBy('name,id');
Will be converted into:
WHERE (name != :name_1178871154 AND id = :id_897328160) GROUP BY name,id
With parameters:
array(8) [ 'name_1178871154' => string (3) "foo" 'id_897328160' => integer 123 ]
Limit and Offset
Pretty straightforward with setLimit and setOffset methods.
$CriteriaBuilder ->whereRaw('foo + bar') ->andWhereRaw('1=1') ->orWhereRaw('foo::bar()'); ->setLimit(10) ->setOffset(5);
Will be converted into:
WHERE (foo + bar AND 1=1 OR foo::bar()) LIMIT 10 OFFSET 5
Order By
Order By is implemented using ASC and DESC keywords, in an associative array with setOrderBy method.
$CriteriaBuilder ->whereRaw('foo + bar') ->andWhereRaw('1=1') ->orWhereRaw('foo::bar()') ->setOrderBy([ 'name' => 'DESC', 'id' => 'ASC' ]);
Will be converted into:
WHERE (foo + bar AND 1=1 OR foo::bar()) ORDER BY name DESC,id ASC
Custom Gluing
Manual Criteria handling is also possible by using the glue methods.
$CriteriaBuilder ->where('id', 'IN', [1,2,3]) ->orWhere('id', 'NOT IN', [4,5,6]) ->glueByOr() ->where('name', '!=', 'foo') ->andWhere('email', '!=', 'foo@bar') ->glueByAnd() ->where('bar', '=', 'bar') ->andWhere('name', '=', 'Doe'); $CriteriaBuilder->setLimit(10); $CriteriaBuilder->setOffset(5); $CriteriaBuilder->setGroupBy('name,id'); $CriteriaBuilder->setOrderBy(['name' => 'DESC', 'id' => 'ASC']);
Will be converted into:
(id IN (:id_1263450107,:id_1088910886,:id_404821955) OR id NOT IN (:id_470739703,:id_562547487,:id_230395754)) OR (name != :name_1409254675 AND email != :name_190021050) AND (bar = :bar_1337676982 AND name = :name_391340793) GROUP BY name,id ORDER BY name DESC,id ASC LIMIT 10 OFFSET 5
With parameters:
array(10) [
'id_470739703' => integer 4
'id_562547487' => integer 5
'id_230395754' => integer 6
'id_1263450107' => integer 1
'id_1088910886' => integer 2
'id_404821955' => integer 3
'name_190021050' => string (3) "foo@bar"
'name_1409254675' => string (3) "foo"
'name_391340793' => string (3) "Doe"
'bar_1337676982' => string (3) "bar"
]
Operators
There are almost 20 operators ready for use like Equal, NotIn, Between or Is. Check them all here.
Equal
$CriteriaBuilder->where('foo', '=', 'bar');
Will output:
WHERE (foo = :foo_1337676681)
NotIn
$CriteriaBuilder->where('foo', 'NOT IN', ['bar', 'buzz']);
Will output:
WHERE (foo NOT IN [:foo_1337676681, :foo_1337776681)
Between
There must be exactly 2 parameters provided or an exception will be thrown.
$CriteriaBuilder->where('foo', 'BETWEEN', ['bar', 'buzz']);
Will output:
WHERE (foo BETWEEN :foo_1337676681 AND :foo_1337776681)
There are many more. See here for more examples.
Custom Operators
You can register your own Operators with:
/** * @param $sql_type * @param $operator_class_name * * @return void */ public static function registerOperator($sql_type, $operator_class_name);
For example:
class OperatorCustomTypeStub extends AbstractOperator { const TYPE_NAME = 'CustomType'; const TYPE_AS_SQL = '<sql for custom operator>'; } Builder::registerOperator(OperatorCustomTypeStub::TYPE_AS_SQL, 'Some\Namespace\OperatorCustomTypeStub');
You can use your own operator with raw methods.
$CriteriaBuilder->whereRaw('bar', null, OperatorCustomTypeStub::TYPE_AS_SQL); $CriteriaBuilder->andWhereRaw('foo', ['foo' => 'bar'], OperatorCustomTypeStub::TYPE_AS_SQL); $CriteriaBuilder->orWhereRaw('foo', 'bar', OperatorCustomTypeStub::TYPE_AS_SQL);
Will output:
WHERE (bar <sql for custom operator> NULL AND foo <sql for custom operator> :foo_1337676981 OR foo <sql for custom operator> :foo_2137676760 )
See https://github.com/oliwierptak/everon-criteria-builder/tree/development/src/Operator for more examples
How to use
Dependency Injection is done with Everon Factory.
Initialize with CriteriaBuilderFactoryWorker->buildCriteriaBuilder().
use Everon\Component\CriteriaBuilder\CriteriaBuilderFactoryWorkerInterface; use Everon\Component\Factory\Dependency\Container; use Everon\Component\Factory\Factory; include('vendor/autoload.php'); $Container = new Container(); $Factory = new Factory($Container); $Factory->registerWorkerCallback('CriteriaBuilderFactoryWorker', function() use ($Factory) { return $Factory->buildWorker(CriteriaBuilderFactoryWorker::class); }); $CriteriaBuilderFactoryWorker = $Factory->getWorkerByName('CriteriaBuilderFactoryWorker'); $CriteriaBuilder = $CriteriaBuilderFactoryWorker->buildCriteriaBuilder();
Setup your conditions.
$CriteriaBuilder ->where('sku', 'LIKE', '13%') ->orWhere('id', 'IN', [1, 2, 3]) ->glueByOr() ->where('created_at', '>', '2015-12-03 12:27:22');
Append criteria string to already existing sql.
$sql = 'SELECT * FROM <TABLE>'; $sql = $sql . (string) $CriteriaBuilder; $sth = $dbh->prepare($sql);
Fetch sample data.
After you attached SQL to the CriteriaBuilder, it's even easier to retrieve sql query and its parameters,
with SqlPartInterface and methods like getSql and getParameters.
$dbh = new \PDO('mysql:host=127.0.0.1;dbname=DATABASE', 'root', ''); $SqlPart = $CriteriaBuilder->toSqlPart(); $sth = $dbh->prepare($SqlPart->getSql()); $sth->execute($SqlPart->getParameters());
Putting it all together
$dbh = new \PDO('mysql:host=127.0.0.1;dbname=DATABASE', 'root', ''); $CriteriaBuilder ->sql('SELECT * FROM fooTable f LEFT JOIN barTable b ON f.bar_id = b.id AND f.is_active = :is_active') ->where('bar', '=', 1) ->andWhere('foo', 'NOT IN', [1,2,3]) ->orWhereRaw('foo::bar() IS NULL') ->setParameter('is_active', false) ->setLimit(10) ->setOffset(20) ->setOrderBy(['foo' => 'DESC']); $SqlPart = $CriteriaBuilder->toSqlPart(); $sth = $dbh->prepare($SqlPart->getSql()); $sth->execute($SqlPart->getParameters()); $data = $sth->fetchAll(PDO::FETCH_ASSOC);