Les règles métiers (comme « tous les clients qui ont dépensé plus de 100€ en une fois reçoivent une réduction de 10% sur leur prochain achat ») sont la plupart du temps définies à l'extérieur de l'application. Elles sont même souvent exprimées dans un langage différent que le langage de programmation utilisé. La bibliothèque `Hoa\Ruler` offre un moteur permettant d'exécuter des règles métiers simplement et de manière performante tout en restant très extensible.

## Table des matières

## Introduction

La logique métier est très différente de la logique Informatique. « Tous les clients qui ont dépensé plus de 100€ en une fois reçoivent une réduction de 10% sur leur prochain achat ». Cette règle permet d'accéder à certaines parties du programme si elle est validée. Toutefois, elle peut changer à n'importe quel moment. Très souvent, dans une équipe, ce ne sera pas au développeur d'implémenter cette règle. Elle proviendra probablement d'un dépôt de règles métiers, qui auront été écrites par d'autres personnes, soit manuellement, soit à l'aide d'un programme tiers. Cela implique que le langage utilisé pour exprimer une règle n'est pas le langage utilisé pour développer le programme. Un exemple encore plus évident avec l'utilisation d'une règle pour filtrer des éléments : un élément est accepté si « son groupe est customer ou guest et son nombre de points est supérieur à 30 ». Cette règle peut très bien être écrite par un utilisateur via une interface en ligne de commande pour filtrer des résultats d'une base de données ou de logs.

Il est important de comprendre que les règles doivent être écrites dans un langage dédié. Néanmoins, l'usage qui est fait des règles est très vaste et imprédictible. C'est pourquoi il est primordiale d'avoir des règles souples et extensibles dans leur syntaxe. Par exemple, il doit être permis d'ajouter des opérateurs ou des fonctions : « tous les clients de l'hôtel avec un pass Gold auront une réduction de 10% ». Le « pass Gold » peut être un opérateur ou une fonction spécifique au métier du programme concerné.

Le langage utilisé par la bibliothèque `Hoa\Ruler` pour décrire des règles respecte ces contraintes d'extensibilité. Les règles ne seront pas proches du langage humain mais resterons naturelles à lire. Si nous reprenons l'exemple de la règle « son groupe est customer ou guest et son nombre de points est supérieur à 30 », elle s'écrira : `group in ["customer", "guest"] and points > 30`. Les éléments `group` et `points` sont des variables de la règle. Leurs valeurs seront définies par un contexte.

D'un point de vue plus formel, une règle est un prédicat, c'est à dire que son résultat est toujours un booléen : `true` ou `false`. Comme ces règles sont destinées à être manipulées (modifiées) et exécutées, la bibliothèque `Hoa\Ruler` propose plusieurs outils pour travailler efficacement avec ces contraintes, présentés dans les sections suivantes.

## Fonctionnement général

Le fonctionnement général de la bibliothèque `Hoa\Ruler` se déroule en 3 étapes :

1. définition d'une règle ;
2. définition d'un contexte ;
3. usage d'un asserteur pour l'exécution.

La règle est une chaîne de caractères respectant une syntaxe précise, décrite par la grammaire du langage définie par la bibliothèque `Hoa\Ruler` (détaillée ci-après). Cette règle contient des variables dont les valeurs sont définies par le contexte. Le contexte peut contenir des valeurs scalaires, des tableaux ou même des fonctions et des objets. Enfin, l'asserteur associe le contexte à la règle pour pouvoir l'exécuter et obtenir un résultat. Nous rappelons que ce résultat est nécessairement un booléen. C'est alors un prédicat.

Le contexte est représenté par la classe `Hoa\Ruler\Context`. L'asserteur est représenté par la classe `Hoa\Ruler\Visitor\Asserter`. Nous pouvons employer la méthode `Hoa\Ruler\Ruler::assert` qui facilite son utilisation. Ainsi :

    $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)
     */

La règle est définie dans la variable `$rule`. Le contexte, quant à lui, est défini dans la variable `$context`. Il contient 2 variables : `group` et `points`, respectivement avec les valeurs `'customer'` et `42` (retournée par une fonction). Enfin, la dernière étape utilise la méthode `Hoa\Ruler\Ruler::assert` pour exécuter la règle `$rule` avec le contexte `$context` (ce dernier est optionnel). Le résultat est `true` car `group` est bien dans la liste `customer` ou `guest`, et `point` est bien supérieur à 30. Changez les valeurs dans le contexte ou la règle pour observer un résultat différent.

Les sections suivantes détaillent le fonctionnement de chaque partie mais l'usage classique reste aussi simple que ça !

### Grammaire (par l'exemple)

La grammaire du langage des règles est décrite dans le fichier `hoa://Library/Ruler/Grammar.pp`. Cette grammaire est exprimée avec le langage PP. Pour plus d'informations, voir la bibliothèque `Hoa\Compiler`. Nous précisons que le langage supporte Unicode. Nous n'allons pas expliquer le langage alors que la grammaire donne tous les détails nécessaires. En revanche, nous allons donner plusieurs exemples de syntaxe.

Langage de `Hoa\Ruler` par l'exemple.
syntaxe | sémantique
--- | ---
`'foo'`, `"foo"`, `'f\'oo'` | des chaînes de caractères
`true`, `false`, `null` | des constantes pré-définies
`4.2` | un réel
`42` | un entier
`['foo', true, 4.2, 42]` | un tableau (hétérogène)
`sum(1, 2, 3)` | un appel de la fonction `sum` avec 3 arguments
`points` | une variable
`points['x']` | un accès tableau
`line.pointA` | un accès objet (attribut)
`line.length()` | un appel à une méthode
`and`, `or`, `xor`, `not` | des opérateurs logiques
`=`, `!=`, `>`, `&lt;`, `>=`, `&lt;=` | des opérateurs de comparaisons
`is`, `in` | des opérateurs d'appartenance

Bien sûr, ces exemples représentent des parties atomiques de la grammaire que nous pouvons combiner. Ainsi : `userA.allows(groups[groupId][userB])` est valide. De même que `f(user, points > 7 and points &lt; 42)` est également valide.

En réalité, les fonctions, les opérateurs de comparaisons et les opérateurs d'appartenance ne sont pas définis par la grammaire mais par l'asserteur (détaillé ci-après). `Hoa\Ruler` ne fait pas la différence entre un opérateur et une fonction. Les opérateurs sont considérés comme des fonctions ; un opérateur n'étant qu'une fonction d'arité 1 ou 2. Ainsi, nous pouvons écrire `2 = 2` ou `=(2, 2)`, cela produira strictement le même résultat. Tout comme le nom des fonctions qui n'est pas défini par la grammaire, le nom des opérateurs n'est lui non plus pas défini par la grammaire, excepté pour les opérateurs logiques qui ont un traitement particuliers (à cause de la précédence des opérateurs). Cela a pour effet de pouvoir créer nos propres opérateurs ou fonctions. Nous pouvons imaginer `a ∈ A`, `√(42)` ou encore `userA allows userB` comme étant des expressions valides.

### Contexte

Le contexte définit les valeurs des variables présentes dans des règles. Ces valeurs peuvent être :

- des constantes, comme `42` ou `'foo'` qui sont des scalaires, ou `[1, 1, 2, 3, 5]` ou un objet qui sont des types structurés ;
- des valeurs calculées, c'est à dire retournée par une fonction ou une méthode.

Par défaut, les valeurs calculées ne le sont qu'une seule fois. En effet, elles sont placées dans un cache pour des raisons de performance. Si nous voulons les recalculer à chaque lecture, il faudra les encapsuler dans un objet de type `Hoa\Ruler\DynamicCallable`.

Avant de détailler cette partie, présentons le contexte. Un contexte est une instance de la classe `Hoa\Ruler\Context` qui implémente l'interface `ArrayAccess`. Ainsi nous utilisons le contexte comme un tableau :

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

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

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

Pour y déposer des valeurs calculées, nous pouvons le faire via une fonction ; ainsi :

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

    var_dump($context['computed']);

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

Nous avons dit que les valeurs calculées sont placées en cache dès la première lecture. Pour l'illustrer, nous allons utiliser une fonction qui incrémente un entier à chaque appel :

    $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)
     */

La variable `$i` a été incrémentée une seule fois pour passer de 0 à 1, puis elle a été placée en cache. Maintenant, si nous encapsulons cette fonction dans une instance de la classe `Hoa\Ruler\DynamicCallable`, observons ce qu'il se passe :

    $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)
     */

Le résultat n'est plus mis en cache.

Nous pouvons également utiliser une fonction déclarée grâce à son nom. Attention toutefois, il sera impossible d'appeler les fonctions natives de PHP pour des raisons de sécurité. Le contexte n'a pas une telle portée. Ainsi :

    function answer()
    {
        return 42;
    }

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

    var_dump($context['the_answer']);

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

C'est tout ce qu'il faut savoir sur le contexte. Ce n'est pas plus compliqué que ça !

### Asserteur

Étant donné une règle et un contexte, l'asserteur est chargé de calculer le résultat de cette règle, dont les valeurs des variables sont dans le contexte.

La règle peut avoir deux formes possibles :

- une chaîne de caractère ou
- un modèle objet.

Si c'est une chaîne de caractère, elle sera transformée en modèle objet automatiquement par la méthode `Hoa\Ruler\Ruler::assert`. Ce modèle objet implémente les interfaces de la bibliothèque `Hoa\Visitor` et peut donc être visité. C'est pourquoi l'asserteur `Hoa\Ruler\Visitor\Asserter` est un visiteur. Le contexte quant à lui est défini auprès de l'asserteur avec la méthode `Hoa\Ruler\Visitor\Asserter::setContext`. Ainsi :

    $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)
     */

La méthode `Hoa\Ruler\Ruler::assert` va automatiquement définir le contexte auprès de l'asserteur.

#### Ajout de fonctions

Nous avons précisé que les noms des opérateurs et des fonctions dans les règles sont libres. Ainsi, nous avons évoqué la possibilité de définir nos propres opérateurs et fonctions. Ajoutons la fonction `logged` qui teste si un objet de type `User` est connecté. Voici cet objet :

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

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

L'implémentation de la fonction `logged` serait alors :

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

Enfin, pour déclarer cette fonction, nous allons utiliser la méthode `Hoa\Ruler\Visitor\Asserter::setOperator`. Nous pouvons aussi citer les méthodes `operatorExists`, `getOperator` et `getOperators` qui permettent respectivement de tester si un opérateur existe, d'obtenir un opérateur précédemment déclaré et d'obtenir tous les opérateurs déclarés. Ainsi :

    $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)
     */

La classe `Hoa\Ruler\Ruler` ne contient que des méthodes pour travailler plus vite et cacher le mécanisme sous-jacent (détaillé ci-après). Elle contient entre autre la méthode statique `getDefaultAsserter` qui retourne une instance unique de la classe `Hoa\Ruler\Visitor\Asserter`. Nous pouvons utiliser cette instance unique pour définir des nouveaux opérateurs pour toutes les règles. Son utilisation est très similaire à ce que nous avons vu précédemment :

    $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)
     */

La méthode `Hoa\Ruler\Visitor\Asserter::setOperator` utilise n'importe quel callable valide.

Les opérateurs `and`, `or`, `xor`, `not`, `=`, `!=`, `sum` etc. sont définis dans la classe `Hoa\Ruler\Visitor\Asserter`. N'hésitez pas à vous en inspirer !

## Transformation du langage

Le mécanisme sous-jacent caché par la classe `Hoa\Ruler\Ruler` est simple et très modulaire. Les sections suivantes détaillent les transformations possibles du langage et leur utilisation.

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, 'règle'), 0, 1); var vinterpreter = grid.push(paper.rect(0, 0, 110, 80, 3, 'interpréteur'), 1, 1); var model = grid.push(paper.rect(0, 0, 110, 80, 3, 'modèle'), 2, 1); var vasserter = grid.push(paper.rect(0, 0, 110, 80, 3, 'asserteur'), 4, 0); var vcompiler = grid.push(paper.rect(0, 0, 110, 80, 3, 'code PHP'), 4, 1); var vdisassembly = grid.push(paper.rect(0, 0, 110, 80, 3, 'règle'), 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, ''); });

Tout d'abord, la règle est interprétée par un interpréteur pour être transformée en modèle objet. Ensuite, ce modèle objet est utilisé par l'asserteur, ou peut être transformé en code PHP ou transformé en une règle. Le modèle objet est le point central du langage, c'est sa forme la plus avancée.

### Interpréteur : langage vers modèle objet

Une règle est une chaîne de caractères. Pour la transformer en modèle objet, nous utilisons la bibliothèque `Hoa\Compiler`.

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, 'règle'), 0, 1); var vinterpreter = grid.push(paper.rect(0, 0, 110, 80, 3, 'interpréteur'), 1, 1); var model = grid.push(paper.rect(0, 0, 110, 80, 3, 'modèle'), 2, 1); var vasserter = grid.push(paper.rect(0, 0, 110, 80, 3, 'asserteur').attr({opacity: .3}), 4, 0); var vcompiler = grid.push(paper.rect(0, 0, 110, 80, 3, 'code PHP').attr({opacity: .3}), 4, 1); var vdisassembly = grid.push(paper.rect(0, 0, 110, 80, 3, 'règle').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, ''); });

Grâce à la grammaire des règles (définie dans le fichier `hoa://Library/Ruler/Grammar.pp`), nous allons obtenir un AST : un arbre abstrait. Par exemple, pour la règle `points > 30`, son AST est :

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

Pour que cet arbre soit exploitable plus facilement, il va être transformé en modèle objet. Cette transformation est assurée par la classe `Hoa\Ruler\Visitor\Interpreter`. Ainsi, si nous devions le faire manuellement :

    $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"
     */

Nous apprenons alors que le modèle est représenté par les classes appartenant à l'espace de nom `Hoa\Ruler\Model`.

Toutes ces opérations sont remplacées par la méthode statique `Hoa\Ruler\Ruler::interpret` :

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

Nous pouvons obtenir le compilateur avec la méthode `Hoa\Ruler\Ruler::getCompiler`.

Nous verrons comment cette étape peut être importante pour améliorer les performances.

### Compilateur : modèle objet vers PHP

Le modèle objet peut être créé manuellement en instanciant tous les objets de type `Hoa\Ruler\Model\*` et en les combinant ensemble.

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, 'règle').attr({opacity: .3}), 0, 1); var vinterpreter = grid.push(paper.rect(0, 0, 110, 80, 3, 'interpréteur').attr({opacity: .3}), 1, 1); var model = grid.push(paper.rect(0, 0, 110, 80, 3, 'modèle'), 2, 1); var vasserter = grid.push(paper.rect(0, 0, 110, 80, 3, 'asserteur').attr({opacity: .3}), 4, 0); var vcompiler = grid.push(paper.rect(0, 0, 110, 80, 3, 'code PHP'), 4, 1); var vdisassembly = grid.push(paper.rect(0, 0, 110, 80, 3, 'règle').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, ''); });

Le code PHP nécessaire à cette opération peut être automatiquement généré grâce à la classe `Hoa\Ruler\Visitor\Compiler`. Ainsi :

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

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

Le code généré est simplifié et optimisé pour être le plus court possible tout en restant lisible par un être humain.

Nous verrons comment cette étape peut être importante pour améliorer les performances.

### Désassembleur : modèle objet vers langage

Jusqu'à maintenant, nous avons vu comment passer d'une règle vers son modèle objet. Le désassembleur applique l'opération inverse : il transforme un modèle objet vers une chaîne de caractères.

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, 'règle').attr({opacity: .3}), 0, 1); var vinterpreter = grid.push(paper.rect(0, 0, 110, 80, 3, 'interpréteur').attr({opacity: .3}), 1, 1); var model = grid.push(paper.rect(0, 0, 110, 80, 3, 'modèle'), 2, 1); var vasserter = grid.push(paper.rect(0, 0, 110, 80, 3, 'asserteur').attr({opacity: .3}), 4, 0); var vcompiler = grid.push(paper.rect(0, 0, 110, 80, 3, 'code PHP').attr({opacity: .3}), 4, 1); var vdisassembly = grid.push(paper.rect(0, 0, 110, 80, 3, 'règle'), 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, ''); });

La règle générée peut différer un peu de la règle originale syntaxiquement (parenthèses, guillemets, espacements…) mais jamais sémantiquement. Ainsi :

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

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

## Performances

Transformer une règle vers un modèle objet a un coût. Ce coût devient significatif si l'opération est appliquée des milliers de fois à la minute. En revanche, appliquer un asserteur sur un modèle objet n'est pas coûteux. Nous allons donc présenter deux façons d'éviter la transformation d'une règle vers un modèle objet.

### Sérialiser le modèle objet

Une fois le modèle objet obtenu, nous pouvons le sérialiser à l'aide de la fonction PHP `serialize`. Nous allons obtenir une chaîne de caractères représentant les instances des objets constituant le modèle objet. Pour obtenir à nouveau le modèle objet et être capable d'y appliquer un asserteur, nous utiliserons la fonction PHP `unserialize`. Le résultat de la sérialisation peut être stocké en base de données à la place des règles. Cela nécessite un peu plus de place mais rappelons que nous pouvons transformer le modèle objet vers sa règle facilement grâce à la classe `Hoa\Ruler\Visitor\Disassembly`, cette information n'est donc pas perdue. Ainsi :

    $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)
     */

De cette manière, la règle n'est transformée en modèle objet qu'une seule fois !

### Enregistrer et exécute du code PHP

Une autre façon d'éviter la transformation d'une règle en modèle objet est d'enregistrer le code PHP permettant de construire le modèle objet grâce à la classe `Hoa\Ruler\Visitor\Compiler`. Une fois ce code PHP enregistré et exécuté, nous retrouverons notre modèle objet.

Toutefois, exécuter un tel code PHP s'avèrera légèrement plus lent et plus difficile à mettre en œuvre que la technique précédente.

## Conclusion

La bibliothèque `Hoa\Ruler` définit un langage de règles métiers simple et inspiré du langage SQL dans sa syntaxe. Le langage peut être transformé de plusieurs façons : vers un modèle objet pour qu'il puisse être exécuté, ou à partir de ce modèle, vers du code PHP ou vers le langage d'origine. L'instanciation des variables dans ce langage se fait à travers un contexte. Toutes ces opérations sont cachées à travers une interface simple et claire.

La question des performances a été abordée et deux solutions sont proposées. Aujourd'hui utilisé dans l'industrie sur de gros projets, `Hoa\Ruler` est capable de supporter de lourdes charges si ces pratiques simples sont mises en œuvre.