Powerful implementation of the Specification pattern in PHP
RulerZ [![Build Status](https://travis-ci.org/K-Phoen/rulerz.svg?branch=master)](https://travis-ci.org/K-Phoen/rulerz) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/K-Phoen/rulerz/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/K-Phoen/rulerz/?branch=master)
======

:warning: **This project is no longer maintained**, reach out to me if you are interested in becoming maintainer :warning:

> The central idea of Specification is to separate the statement of how to match
> a candidate, from the candidate object that it is matched against.
>
> Specifications, [explained by Eric Evans and Martin Fowler](http://www.martinfowler.com/apsupp/spec.pdf)

RulerZ is a PHP implementation of the **Specification pattern** which puts the
emphasis on three main aspects:

 * an easy and **data-agnostic [DSL](http://en.wikipedia.org/wiki/Domain-specific_language)**
   to define business rules and specifications,
 * the ability to check if a candidate **satisfies** a specification,
 * the ability to **filter or query any datasource** to only retrieve
   candidates matching a specification.


Introduction
------------

Business rules can be written as text using a dedicated language, very close to
SQL, in which case we refer to them as *rules* or they can be encapsulated in
single classes and referred to as *specifications*.

Once a rule (or a specification) is written, it can be used to check if a single
candidate satisfies it or directly to query a datasource.

The following datasources are supported natively:

 * array of arrays,
 * array of objects.

And support for each one of these is provided by an additional library:

 * Doctrine DBAL QueryBuilders: [rulerz-php/doctrine-dbal](https://github.com/rulerz-php/doctrine-dbal/),
 * Doctrine ORM QueryBuilders: [rulerz-php/doctrine-orm](https://github.com/rulerz-php/doctrine-orm/),
 * [Pomm](http://www.pomm-project.org/) models: [rulerz-php/pomm](https://github.com/rulerz-php/pomm/),
 * Elasticsearch (using the [official client](https://github.com/elasticsearch/elasticsearch-php): [rulerz-php/elasticsearch](https://github.com/rulerz-php/elasticsearch/),
 * Solr (using the [solarium](https://github.com/solariumphp/solarium): [rulerz-php/solarium](https://github.com/rulerz-php/solarium/),
 * Laravel's [Eloquent ORM](http://laravel.com/docs/5.0/eloquent): [rulerz-php/eloquent](https://github.com/rulerz-php/eloquent/).

**Killer feature:** when working with Doctrine, Pomm, or Elasticsearch, RulerZ
is able to convert rules directly in queries and does not need to fetch data
beforehand.

#### That's cool, but why do I need that?

First of all, you get to **express business rules** in a dedicated, **simple
language**.
Then, these business rules can be **encapsulated** in specification classes, reused
and composed to form more complex rules. Specifications are now **reusable** and
**testable**.
And last but not least, these rules can be used both to check if a candidate
satisfies it and to **filter any datasource**.

If you still need to be conviced, you can read the whole reasoning in [this
article](http://blog.kevingomez.fr/2015/02/07/on-taming-repository-classes-in-doctrine-among-other-things/).


Quick usage
-----------

As a quick overview, we propose to see a little example that manipulates a
simple rule and several datasources.

#### 1. Write a rule

The rule hereafter describes a "*high ranked female player*" (basically, a female
player having more than 9000 points).

```php
$highRankFemalesRule = 'gender = "F" and points > 9000';
```

#### 2. Define a datasource

We have the following datasources:

```php
// a Doctrine QueryBuilder
$playersQb = $entityManager
    ->createQueryBuilder()
    ->select('p')
    ->from('Entity\Player', 'p');

// or an array of arrays
$playersArr = [
    ['pseudo' => 'Joe',   'gender' => 'M', 'points' => 2500],
    ['pseudo' => 'Moe',   'gender' => 'M', 'points' => 1230],
    ['pseudo' => 'Alice', 'gender' => 'F', 'points' => 9001],
];

// or an array of objects
$playersObj = [
    new Player('Joe',   'M', 40, 2500),
    new Player('Moe',   'M', 55, 1230),
    new Player('Alice', 'F', 27, 9001),
];
```

#### 3. Use a rule to query a datasource

For any of our datasource, retrieving the results is as simple as calling the
`filter` method:

```php
// converts the rule in DQL and makes a single query to the DB
$highRankFemales = $rulerz->filter($playersQb, $highRankFemalesRule);
// filters the array of arrays
$highRankFemales = $rulerz->filter($playersArr, $highRankFemalesRule);
// filters the array of objects
$highRankFemales = $rulerz->filter($playersObj, $highRankFemalesRule);
```

#### 3. (bis) Check if a candidate satisfies a rule

Given a candidate, checking if it satisfies a rule boils down to calling the
`satisfies` method:

```php
$isHighRankFemale = $rulerz->satisfies($playersObj[0], $highRankFemalesRule);
```

Going further
-------------

Check out [the documentation](doc/index.md) to discover what RulerZ can do for
you.


License
-------

This library is under the [MIT](LICENSE) license.
