Business rules (like “all the customers that have reached 100€ in one time receive a voucher of 10% on the next purchase”) are most of the time defined outside the application. They are even often written in a different language than the used programming language. The `Hoa\Ruler` library provides an engine allowing to simply execute business rules while being efficient and very extensible.

## Table of contents

## Introduction

The business logic is very different from the Computer logic. “All the customers that have reached 100€ in one time receive a voucher of 10% on the next purchase”. This rule allows to access to certain parts of the program if valids. However, it can change at any moment. Most of the time, in a team, it is not the role of the developer to implement this rule. It will probably come from a business rules repository, which have been written by other persons, either manually or thanks to a third-party program. This implies that the language used to express a rule is not the language used to develop the program. An even more obvious example is the use of a rule to filter elements: An element is accepted if “its group is customer or guest and its number of points is greater than 30”. This rule can be written by a user via a command line interface to filter results from a database or logs.

This is important to understand that rules must be written in a dedicated language. Nevertheless, the way we use rules is very vast and unpredictable. This is why it is primordial to have flexible and extensible rules in the syntax. For instance, it should be allowed to add operators and functions: “All the customers from the hotel with a Gold pass will receive a voucher of 10%”. The “Gold pass” can be an operator or a function specific to the current business.

The language the `Hoa\Ruler` library uses to describe rules respects these constraints of extensibility. The rules will not be close to the human language but they will stay natural when reading. If we take the example of the “its group is customer or guest and its number of points is greater than 30”, it will be written: `group in ["customer", "guest"] and points > 30`. The `group` and `points` elements are variables of the rule. Their values will be defined in a context.

From a more formal point of view, a rule is a predicate, it means that its result is always a boolean: `true` or `false`. Because these rules are likely to be manipulated (modified) and executed, the `Hoa\Ruler` library provides several tools to work efficiently with these constraints, presented in the following sections.

## Global workflow

The global workflow of the `Hoa\Ruler` library follows 3 steps:

1. Defining a rule,
2. Defining a context,
3. Use of an asserter for the execution.

The rule is a string matching a specific syntax, which is described by the grammar of the language defined by the `Hoa\Ruler` library (detailed hereinafter). This rule contains variables whose values are defined by the context. The context can contain scalar values, arrays or even functions or objects. Finally, the asserter associates the context to the rule in order to execute it and to obtain a result. We remind you about the result which is necessarily a boolean. This is therefore a predicate.

The context is represented by the `Hoa\Ruler\Context` class. The asserter is represented by the `Hoa\Ruler\Visitor\Asserter` class. We can use the `Hoa\Ruler\Ruler::assert` method to ease its usage. Thus:

    $ruler = new Hoa\Ruler\Ruler();

    // 1. Write a rule.
    $rule  = 'group in ["customer", "guest"] and points > 30';

    // 2. Create a context.
    $context           = new Hoa\Ruler\Context();
    $context['group']  = 'customer';
    $context['points'] = function () {
        return 42;
    };

    // 3. Assert!
    var_dump(
        $ruler->assert($rule, $context)
    );

    /**
     * Will output:
     *     bool(true)
     */

The rule is defined in the `$rule` variable. The context, as far as it is concerned, is defined in the `$context` variable. It contains 2 variables: `group` and `point`, respectively with the `'customer'` and `42` (returned by a function) values. Finally, the last step uses the `Hoa\Ruler\Ruler::assert` method to execute the `$rule` rule with the `$context` context (this latter is optional). The result is `true` because `group` belongs to the list `customer` or `guest`, and `point` is greater than 30. Change the values in the context or the rule to observe a different result.

The following sections detail the behavior of each step but the classical usage remains as simple as that!

### Grammar (through examples)

The grammar of the rules language is described in the `hoa://Library/Ruler/Grammar.pp` file. This grammar is expressed with the PP language. To get more information, please see the `Hoa\Compiler` library. We clarify that the language supports Unicode. We are not going to explain the language whilst the grammar provides all the necessary details. Nevertheless, we are going to give several syntax examples.

Language of `Hoa\Ruler` through examples.
syntax | semantics
--- | ---
`'foo'`, `"foo"`, `'f\'oo'` | strings
`true`, `false`, `null` | pre-defined constants
`4.2` | a real
`42` | an integer
`['foo', true, 4.2, 42]` | an array (heterogeneous)
`sum(1, 2, 3)` | a call to the `sum` function with 3 arguments
`points` | a variable
`points['x']` | an array access
`line.pointA` | an object access (attribute)
`line.length()` | a call to a method
`and`, `or`, `xor`, `not` | logical operators
`=`, `!=`, `>`, `&lt;`, `>=`, `&lt;=` | comparison operators
`is`, `in` | membership operators

Of course, these examples represent atomic parts of the grammar that we can combine. Thus: `userA.allows(groups[groupId][userB])` is valid. Just like `f(user, points > 7 and points &lt; 42)` which is also valid.

In actual fact, functions, comparison operators and membership operators are not defined by the grammar but by the asserter (detailed hereinafter). `Hoa\Ruler` does not make any difference between an operator and a function. Operators are considered as functions; an operator only being a function with an arity of 1 or 2. Thus, we can write `2 = 2` or `=(2, 2)`, this will strictly produce the same result. Just like the name of functions that is not defined in the grammar, the name of operators is neither defined by the grammar, excepted for the logical operators that have a particular processing (because of the operator precedence). The immediate result is that we can create our own operators or functions. We can imagine `a ∈ A`, `√(42)` or even `userA allows userB` being valid expressions.

### Context

The context defines values of variables present in rules. These values can be of kind:

- Constants, like `42` or `'foo'` which are scalars, or `[1, 1, 2, 3, 5]` or an object which are structured types,
- Computed values, it means returned by a function or a method.

By default, the computed values are computed only once. Indeed, they are stored in a cache for performance reasons. If we would like to recompute them at each read, an encapsulation in an object of kind `Hoa\Ruler\DynamicCallable` is required.

Before detailing this part, let's present the context. A context is an instance of the `Hoa\Ruler\Context` class which implements the `ArrayAccess` interface. Thus, we use the context like an array:

    $context        = new Hoa\Ruler\Context();
    $context['key'] = 'value';

    var_dump(
        isset($context['key']),
        $context['key']
    );

    /**
     * Will output:
     *     bool(true)
     *     string(5) "value"
     */

To register computed values, we can use a function; thus:

    $context['computed'] = function () {
        return 42;
    };

    var_dump($context['computed']);

    /**
     * Will output:
     *     int(42)
     */

We said computed values are stored in a cache on the first read. To illustrate this we will use a function incrementing an integer at each call:

    $i              = 0;
    $context['int'] = function () use (&amp;$i) {
        return ++$i;
    };

    var_dump(
        $context['int'],
        $context['int'],
        $context['int'],
        $i
    );

    /**
     * Will output:
     *     int(1)
     *     int(1)
     *     int(1)
     *     int(1)
     */

The `$i` variable has been incremented once to go from 0 to 1, and then, has been stored in a cache. Now, if we encapsulate this function in an instance of the `Hoa\Ruler\DynamicCallable` class, let's observe what happens:

    $i              = 0;
    $context['int'] = new Hoa\Ruler\DynamicCallable(
        function () use (&amp;$i) {
            return ++$i;
        }
    );

    var_dump(
        $context['int'],
        $context['int'],
        $context['int'],
        $i
    );

    /**
     * Will output:
     *     int(1)
     *     int(2)
     *     int(3)
     *     int(3)
     */

The result is no longer stored in a cache.

We can also use a declared function thanks to its name. Attention, it is not possible to call native PHP functions for security reasons. The context has not such a scope. Thus:

    function answer()
    {
        return 42;
    }

    $context['the_answer'] = 'answer';

    var_dump($context['the_answer']);

    /**
     * Will output:
     *     int(42)
     */

Nothing more to know about the context. It is not that complicated!

### Asserter

Given a rule and a context, the asserter is responsible to compute the result of a rule, including the values of variables which are in the context.

The rule can have two different forms:

- a string or
- an object model.

If it is a string, it will be transformed into an object model automatically by the `Hoa\Ruler\Ruler::assert` method. This object model implements the interfaces of the `Hoa\Visitor` library and thus can be visited. This is why the `Hoa\Ruler\Visitor\Asserter` asserter is a visitor. Finally, the context is defined on the asserter with the `Hoa\Ruler\Visitor\Asserter::setContext` method. Thus:

    $ruler             = new Hoa\Ruler\Ruler();
    $rule              = 'points > 30';
    $context           = new Hoa\Ruler\Context();
    $context['points'] = 42;

    // Define an asserter.
    $asserter          = new Hoa\Ruler\Visitor\Asserter();

    // Set this asserter on the ruler.
    $ruler->setAsserter($asserter);

    // Assert!
    var_dump(
        $ruler->assert($rule, $context)
    );

    /**
     * Will output:
     *     bool(true)
     */

The `Hoa\Ruler\Ruler::assert` method will automatically define the context on the asserter.

#### Add functions

We said the names of the operators and of the functions in the rules are free. Therefore, we supposed the ability to define our own operators and functions. Let's add the `logged` function that tests if an object of kind `User` is connected. Here is this object:

    class User
    {
        const DISCONNECTED = 0;
        const CONNECTED    = 1;
        protected $_status = 1;

        public function getStatus()
        {
            return $this->_status;
        }
    }

The implementation of the `logged` function might be the following:

    $logged = function (User $user) {
        return $user::CONNECTED === $user->getStatus();
    };

Finally, to declare this function, we will use the `Hoa\Ruler\Visitor\Asserter::setOperator` method. We can also cite the `operatorExists`, `getOperator` and `getOperators` methods which respectively allow to test if an operator exists, to get a previously declared operator and to get all the declared operators. Thus:

    $ruler             = new Hoa\Ruler\Ruler();
    $rule              = 'logged(user) and points > 30';
    $context           = new Hoa\Ruler\Context();
    $context['user']   = new User();
    $context['points'] = 42;

    // Declare the `logged` function.
    $asserter = new Hoa\Ruler\Visitor\Asserter();
    $asserter->setOperator('logged', $logged);

    $ruler->setAsserter($asserter);

    // Assert!
    var_dump(
        $ruler->assert($rule, $context)
    );

    /**
     * Will output:
     *     bool(true)
     */

The `Hoa\Ruler\Ruler` class only contains methods to work faster and to hide the underlying mechanism (detailed hereinafter). One of them is the `getDefaultAsserter` static method which returns a unique instance of the `Hoa\Ruler\Visitor\Asserter` class. We can use this unique instance to define new operators for all the rules. Its usage is very similar to what we saw previously:

    $ruler             = new Hoa\Ruler\Ruler();
    $rule              = 'logged(user) and points > 30';
    $context           = new Hoa\Ruler\Context();
    $context['user']   = new User();
    $context['points'] = 42;

    // Declare the `logged` function.
    $ruler->getDefaultAsserter()->setOperator('logged', $logged);

    // Assert!
    var_dump(
        $ruler->assert($rule, $context)
    );

    /**
     * Will output:
     *     bool(true)
     */

The `Hoa\Ruler\Visitor\Asserter::setOperator` method accepts any valid callable.

The `and`, `or`, `xor`, `not`, `=`, `!=` and `sum` etc. operators are defined in the `Hoa\Ruler\Visitor\Asserter` class. Feel free to read it to get inspired!

## Language transformation

The underlying mechanism hidden by the `Hoa\Ruler\Ruler` class is simple and very modular. The following sections detail the possible transformations, and associated usage, of the language.

Hoa.Document.onReady(function ( ) {

var paper = Hoa.Graph(Hoa.$('#transformation'), 800, 300); var grid = paper.grid(0, 0, 800, 300, 5, 3); var rule = grid.push(paper.rect(0, 0, 110, 80, 3, 'rule'), 0, 1); var vinterpreter = grid.push(paper.rect(0, 0, 110, 80, 3, 'interpreter'), 1, 1); var model = grid.push(paper.rect(0, 0, 110, 80, 3, 'model'), 2, 1); var vasserter = grid.push(paper.rect(0, 0, 110, 80, 3, 'asserter'), 4, 0); var vcompiler = grid.push(paper.rect(0, 0, 110, 80, 3, 'PHP code'), 4, 1); var vdisassembly = grid.push(paper.rect(0, 0, 110, 80, 3, 'rule'), 4, 2);

paper.link.between(rule, vinterpreter, ''); paper.link.between(vinterpreter, model, ''); paper.link.between(model, vasserter, ''); paper.link.between(model, vcompiler, ''); paper.link.between(model, vdisassembly, ''); });

First of all, the rule is interpreted by an interpreter to be transformed into an object model. Then, this object model is used by the asserter, or can be transformed into PHP code or transformed into a rule. This object model is the central point of the language, this is its most advanced form.

### Interpreter: Language to object model

A rule is a string. To transform it into an object model, we will use the `Hoa\Compiler` library.

Hoa.Document.onReady(function ( ) {

var paper = Hoa.Graph(Hoa.$('#transformation_interpreter'), 800, 300); var grid = paper.grid(0, 0, 800, 300, 5, 3); var rule = grid.push(paper.rect(0, 0, 110, 80, 3, 'rule'), 0, 1); var vinterpreter = grid.push(paper.rect(0, 0, 110, 80, 3, 'interpreter'), 1, 1); var model = grid.push(paper.rect(0, 0, 110, 80, 3, 'model'), 2, 1); var vasserter = grid.push(paper.rect(0, 0, 110, 80, 3, 'asserter').attr({opacity: .3}), 4, 0); var vcompiler = grid.push(paper.rect(0, 0, 110, 80, 3, 'PHP code').attr({opacity: .3}), 4, 1); var vdisassembly = grid.push(paper.rect(0, 0, 110, 80, 3, 'rule').attr({opacity: .3}), 4, 2);

paper.link.between(rule, vinterpreter, ''); paper.link.between(vinterpreter, model, ''); paper.link.between(model, vasserter, ''); paper.link.between(model, vcompiler, ''); paper.link.between(model, vdisassembly, ''); });

Thanks to the grammar of rules (defined in the `hoa://Library/Ruler/Grammar.pp` file), we will get an AST: An abstract syntax tree. For instance, for the rule `points > 30`, its AST is:

    $ echo 'points > 30' | hoa compiler:pp hoa://Library/Ruler/Grammar.pp 0 --visitor dump
    >  #expression
    >  >  #operation
    >  >  >  token(identifier, points)
    >  >  >  token(identifier, >)
    >  >  >  token(integer, 30)

In order to be exploitable, this tree will be transformed into an object model. This transformation is ensured by the `Hoa\Ruler\Visitor\Interpreter` visitor. Thus, if we should apply it manually:

    $compiler    = Hoa\Compiler\Llk::load(
        new Hoa\File\Read('hoa://Library/Ruler/Grammar.pp')
    );
    $ast         = $compiler->parse('points > 30');
    $interpreter = new Hoa\Ruler\Visitor\Interpreter();
    $model       = $interpreter->visit($ast);

    var_dump(
        get_class($model)
    );

    /**
     * Will output:
     *     string(21) "Hoa\Ruler\Model\Model"
     */

We learn that the model is represented by classes belonging to the `Hoa\Ruler\Model` namespace.

All these operations are replaced by the `Hoa\Ruler\Ruler::interpret` static method.

    $model = Hoa\Ruler\Ruler::interpret('points > 30');

We can get the compiler with the `Hoa\Ruler\Ruler::getCompiler` method.

We will see how this step can be important to get better performances.

### Compiler: Object model to PHP

The object model can be created manually by instanciating all the objects of kind `Hoa\Ruler\Model\*` and by combining them together.

Hoa.Document.onReady(function ( ) {

var paper = Hoa.Graph(Hoa.$('#transformation_compiler'), 800, 300); var grid = paper.grid(0, 0, 800, 300, 5, 3); var rule = grid.push(paper.rect(0, 0, 110, 80, 3, 'rule').attr({opacity: .3}), 0, 1); var vinterpreter = grid.push(paper.rect(0, 0, 110, 80, 3, 'interpreter').attr({opacity: .3}), 1, 1); var model = grid.push(paper.rect(0, 0, 110, 80, 3, 'model'), 2, 1); var vasserter = grid.push(paper.rect(0, 0, 110, 80, 3, 'asserter').attr({opacity: .3}), 4, 0); var vcompiler = grid.push(paper.rect(0, 0, 110, 80, 3, 'PHP code'), 4, 1); var vdisassembly = grid.push(paper.rect(0, 0, 110, 80, 3, 'rule').attr({opacity: .3}), 4, 2);

paper.link.between(rule, vinterpreter, ''); paper.link.between(vinterpreter, model, ''); paper.link.between(model, vasserter, ''); paper.link.between(model, vcompiler, ''); paper.link.between(model, vdisassembly, ''); });

The PHP code required for this operation can be automatically generated thanks to the `Hoa\Ruler\Visitor\Compiler` class. Thus:

    $compiler = new Hoa\Ruler\Visitor\Compiler();
    echo $compiler->visit($model);

    /**
     * Will output:
     *     $model = new \Hoa\Ruler\Model();
     *     $model->expression =
     *         $model->{'>'}(
     *             $model->variable('points'),
     *             30
     *         );
     */

The generated code is simplified and optimized to be as short as possible whilst staying readable for a human.

We will see how this step can be important to get better performances.

### Disassembler: Object model to language

So far, we have seen how to jump from a rule to its object model. The disassembler applies the opposite operation: It transforms an object model into a string.

Hoa.Document.onReady(function ( ) {

var paper = Hoa.Graph(Hoa.$('#transformation_disassembler'), 800, 300); var grid = paper.grid(0, 0, 800, 300, 5, 3); var rule = grid.push(paper.rect(0, 0, 110, 80, 3, 'rule').attr({opacity: .3}), 0, 1); var vinterpreter = grid.push(paper.rect(0, 0, 110, 80, 3, 'interpreter').attr({opacity: .3}), 1, 1); var model = grid.push(paper.rect(0, 0, 110, 80, 3, 'model'), 2, 1); var vasserter = grid.push(paper.rect(0, 0, 110, 80, 3, 'asserter').attr({opacity: .3}), 4, 0); var vcompiler = grid.push(paper.rect(0, 0, 110, 80, 3, 'PHP code').attr({opacity: .3}), 4, 1); var vdisassembly = grid.push(paper.rect(0, 0, 110, 80, 3, 'rule'), 4, 2);

paper.link.between(rule, vinterpreter, ''); paper.link.between(vinterpreter, model, ''); paper.link.between(model, vasserter, ''); paper.link.between(model, vcompiler, ''); paper.link.between(model, vdisassembly, ''); });

The generated rule can differ a little bit from the original one in term of syntax (parenthesis, quotes, spacing…) but never in term of semantics. Thus:

    $disassembly = new Hoa\Ruler\Visitor\Disassembly();
    echo $disassembly->visit($model);

    /**
     * Will output:
     *     (points > 30)
     */

## Performances

Transforming a rule into an object model is not a low-cost operation. It becomes significant when applied thousand times per minute. Nevertheless, applying an asserter on an object model is a low-cost operation. We will present two ways to avoid the transformation of a rule into an object model.

### Serialize the object model

Once the object model is present, we can serialize it with the help of the `serialize` PHP function. We will get a string representing instances of objects forming the object model. To get back to the object model and being able to apply an asserter, we will use the `unserialize` PHP function. The result of this serialization can be stored in a database instead of rules. This requires a little bit more space but let's remind that we can transform an object model to its rule easily thanks to the `Hoa\Ruler\Visitor\Disassembly` class, consequently this information is not lost. Thus:

    $ruler             = new Hoa\Ruler\Ruler();
    $rule              = 'points > 30';
    $context           = new Hoa\Ruler\Context();
    $context['points'] = 42;

    // Nothing in the database.
    if (null === $serialized = $database->get($ruleId)) {
        // We transform the rule into an object model.
        $model = Hoa\Ruler\Ruler::interpret($rule);

        // We serialize and save the object model.
        $database->save($ruleId, serialize($model));
    } else {
        // We have a serialization! We unserialize it to get the object model.
        $model = unserialize($serialized);
    }

    // We can assert by using a model instead of a rule!
    var_dump(
        $ruler->assert($model, $context)
    );

    /**
     * Will output:
     *     bool(true)
     */

This way, the rule is transformed into an object model only once!

### Save and execute PHP code

Another way to avoid the transformation of a rule into its object model is to save the PHP code allowing to build the object model thanks to the `Hoa\Ruler\Visitor\Compiler` class. Once this PHP code stored and executed, we get back our object model.

However, executing such a PHP code will prove to be slightly slower and more difficult to deploy than the previous technique.

## Conclusion

The `Hoa\Ruler` library defines a language of simple business rules inspired by the SQL language for the syntax. The language can be transformed in many ways: To an object model in order to be executed, or from this object model, into PHP code or into the original language. The instanciation of the variables present in the language relies on a context. All these operations are hidden through a simple and clear interface.

The performance aspect has been addressed and two solutions have been proposed. Today used by the industry on important projects, `Hoa\Ruler` is able to support heavy loads if these simple methodologies are applied.