hawara / criba
A generic implementation of the criteria pattern
Requires (Dev)
- illuminate/database: ^10.28
- laravel/pint: ^1.13
- pestphp/pest: ^2.23
- psy/psysh: ^0.11.21
Suggests
- illuminate/database: Allows using Eloquent query builder to run the criteria.
README
Criba is a PHP implementation of the criteria pattern. It facilitates searching, sorting and paginating records by offering a class based API that defines certain filters so they can latter be applied.
Introduction
The main idea behind the criteria pattern is to separate the definition of a certain filter, sorting or pagination definition from its application in an actual database query. This is particularly interesting when you are writting code following at least some of the domain driven design approaches.
Context
I started this repository when I was following some CodelyTV courses. This video in particular may be helpful to add some context:
Define a criteria
A criteria is the combination of a filter, an order by clause and a pagination criteria.
class Criteria { public readonly Filter $filter; public readonly OrderBy $orderBy; public readonly Page $page; // ... }
See: Criteria.php.
An example with orders
Let's say you have an order class with a property amount and you want to implement a view that shows the latest 10 orders with an amount greater or equal than 1000 (of whatever currency). First, you implement the criteria without any concern about how it will be translated to a particular database engine.
new Criba\Criteria( new Criba\Filter( new Criba\Condition('amount', '>=', 1000), ), new Criba\OrderBy(['order_date' => 'asc']), new Criba\Page(10, 0) );
This example shows the simpler possible example of a filter, where it only consists in a single condition.
Negate a condition
As you may imagine from the example above, conditions require a field name, an operator and a value. But they also accept an additional negate
boolean that can be used to invert them.
// we added the name of the arguments here for readability new Criba\Condition(field: 'amount', operator: '<', value: 1000, negate: true); // or in short form new Criba\Condition('amount', '<', 1000, true);
See: Condition.php.
Comparing fields
In order to compare two fields, rather than a field with a value, you can use comparisons, which can be used wherever conditions can be used.
new Criba\Criteria( new Criba\Filter( new Criba\Comparison('amount_due', '>', 'amount_paid'), ), );
See: Comparison.php.
Joining conditions and comparisons
To define more complex filters, conditions and comparisons can be joined with either and
or or
.
new Criba\Criteria( new Criba\Filter( new Criba\Condition('amount', '>=', 1000), 'and', new Criba\Comparison('amount_due', '>', 'amount_paid'), ), new Criba\OrderBy(['order_date' => 'asc']), new Criba\Page(10, 0) );
This example shows how filters support three arguments:
- the 1st and 3rd argument, must be conditions, comparisons or other filters (see more examples below),
- the 2nd argument bust be one of the strings:
and
oror
.
Going crazy with recursion
The most powerful thing of this package is the possibility of using other filters in the place of conditions and comparisons.
$confirmed = new Criba\Filter( new Criba\Comparison('amount_due', '>', 'amount_paid'), 'or', new Criba\Condition('status', '=', 'confirmed'), parentheses: true ), new Criba\Criteria( new Criba\Filter( new Criba\Condition('amount', '>=', 1000), 'and', $confirmed ), new Criba\OrderBy(['order_date' => 'asc']), new Criba\Page(10, 0) );
See: Filter.php.
Pushing a criteria to a specification
What's nice about the class based API is that if we were happy with that implementation and we wanted to push it to our code so that it represents part of our business logic, we could simply wrap it around a class.
class TopOrdersCriteria extends Criba\Criteria { public function __construct() { parent::__construct( new Criba\Filter( new Criba\Condition('amount', '>=', 1000), ), new Criba\OrderBy(['order_date' => 'asc']), new Criba\Page(10, 0) ); } }
- The specification design pattern (Spanish)
Use a criteria definition
The thing about using a criteria is that it's different for every database. This package offers an already working class that translates any criteria to an Eloquent query.
use Criba\Infrastructure\Eloquent\EloquentCriteriaBuilder; $builder = new EloquentCriteriaBuilder(App\Models\Order::class); /** @var Illuminate\Database\Schema\Builder $query */ $query = $builder->query(new TopOrdersCriteria); $query->get()->all();
At this moment, only an Eloquent implementation has been written.
See: CountryRepository.php to check how this fits when used with the repository pattern (in a domain driven design example).
Install
Via Composer (recommended for production)
To be able to use the Criba classes in your PHP project, the easiest and recommended way is to require it via Composer.
composer require hawara/criba
Via GitHub (recommended for development)
If instead you want to modify this package itself (for instance, to send a pull request), you are encouraged to clone this repository using Git.
git clone https://github.com/hawara-es/criba.git composer install # optionally, you can install the package in production mode # but you won't be able to run the test suite composer install --no-dev
Dependencies
This package does only have two types of dependencies:
- the suggested, which are not actual dependencies as you may not use them,
- the development ones, which you will only need if you are going to participate in the package development.
In practice, it means that if you install it in production mode, no dependencies will be installed.
Open a debug session
The PsySH debugger is installed as a Composer dependency, what means that you can quickly open an interactive PHP session to test a criteria by running:
vendor/bin/psysh
The session will autoload the project namespaces, so you can directly run new Criba\Criteria
without even adding a use statement.
Run the tests
The Pest test engine helps tests being beautiful, readable, quick and are integrated into a GitHub workflow, so every pull request will run them.
vendor/bin/pest
Fix the code style
The Pint code style fixer has been set up to facilitate following Laravel's suggested coding styles.
vendor/bin/pint