Giter Club home page Giter Club logo

ruler's Introduction

Hoa


Build status Code coverage Packagist License

Hoa is a modular, extensible and structured set of PHP libraries.
Moreover, Hoa aims at being a bridge between industrial and research worlds.

Hoa\Ruler

Help on IRC Help on Gitter Documentation Board

This library allows to manipulate a rule engine. Rules can be written by using a dedicated language, very close to SQL. Therefore, they can be written by a user and saved in a database.

Such rules are useful, for example, for commercial solutions that need to manipulate promotion or special offer rules written by a user. To quote Wikipedia:

A business rules engine is a software system that executes one or more business rules in a runtime production environment. The rules might come from legal regulation (“An employee can be fired for any reason or no reason but not for an illegal reason”), company policy (“All customers that spend more than $100 at one time will receive a 10% discount”), or other sources. A business rule system enables these company policies and other operational decisions to be defined, tested, executed and maintained separately from application code.

Learn more.

Installation

With Composer, to include this library into your dependencies, you need to require hoa/ruler:

$ composer require hoa/ruler '~2.0'

For more installation procedures, please read the Source page.

Testing

Before running the test suites, the development dependencies must be installed:

$ composer install

Then, to run all the test suites:

$ vendor/bin/hoa test:run

For more information, please read the contributor guide.

Quick usage

As a quick overview, we propose to see a very simple example that manipulates a simple rule with a simple context. After, we will add a new operator in the rule. And finally, we will see how to save a rule in a database.

Three steps

So first, we create a context with two variables: group and points, and we then assert a rule. A context holds values to concretize a rule. A value can also be the result of a callable. 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)
 */

In the next example, we have a User object and a context that is populated dynamically (when the user variable is concretized, two new variables, group and points are created). Moreover, we will create a new operator/function called logged. There is no difference between an operator and a function except that an operator has two operands (so arguments).

Adding operators and functions

For now, we have the following operators/functions by default: and, or, xor, not, = (is as an alias), !=, >, >=, <, <=, in and sum. We can add our own by different way. The simplest and volatile one is given in the following example. Thus:

// The User object.
class User
{
    const DISCONNECTED = 0;
    const CONNECTED    = 1;

    public $group      = 'customer';
    public $points     = 42;
    protected $_status = 1;

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

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

// New rule.
$rule  = 'logged(user) and group in ["customer", "guest"] and points > 30';

// New context.
$context         = new Hoa\Ruler\Context();
$context['user'] = function () use ($context) {
    $user              = new User();
    $context['group']  = $user->group;
    $context['points'] = $user->points;

    return $user;
};

// We add the logged() operator.
$ruler->getDefaultAsserter()->setOperator('logged', function (User $user) {
    return $user::CONNECTED === $user->getStatus();
});

// Finally, we assert the rule.
var_dump(
    $ruler->assert($rule, $context)
);

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

Also, if a variable in the context is an array, we can access to its values from a rule with the same syntax as PHP. For example, if the a variable is an array, we can write a[0] to access to the value associated to the 0 key. It works as an hashmap (PHP array implementation), so we can have strings & co. as keys. In the same way, if a variable is an object, we can call a method on it. For example, if the a variable is an array where the value associated to the first key is an object with a foo method, we can write: a[0].foo(b) where b is another variable in the context. Also, we can access to the public attributes of an object. Obviously, we can mixe array and object accesses. Please, take a look at the grammar (hoa://Library/Ruler/Grammar.pp) to see all the possible constructions.

Saving a rule

Now, we have two options to save the rule, for example, in a database. Either we save the rule as a string directly, or we will save the serialization of the rule which will avoid further interpretations. In the next example, we see how to serialize and unserialize a rule by using the Hoa\Ruler\Ruler::interpret static method:

$database->save(
    serialize(
        Hoa\Ruler\Ruler::interpret(
            'logged(user) and group in ["customer", "guest"] and points > 30'
        )
    )
);

And for next executions:

$rule = unserialize($database->read());
var_dump(
    $ruler->assert($rule, $context)
);

When a rule is interpreted, its object model is created. We serialize and unserialize this model. To see the PHP code needed to create such a model, we can print the model itself (as an example). Thus:

echo Hoa\Ruler\Ruler::interpret(
    'logged(user) and group in ["customer", "guest"] and points > 30'
);

/**
 * Will output:
 *     $model = new \Hoa\Ruler\Model();
 *     $model->expression =
 *         $model->and(
 *             $model->func(
 *                 'logged',
 *                 $model->variable('user')
 *             ),
 *             $model->and(
 *                 $model->in(
 *                     $model->variable('group'),
 *                     [
 *                         'customer',
 *                         'guest'
 *                     ]
 *                 ),
 *                 $model->{'>'}(
 *                     $model->variable('points'),
 *                     30
 *                 )
 *             )
 *         );
 */

Have fun!

Documentation

The hack book of Hoa\Ruler contains detailed information about how to use this library and how it works.

To generate the documentation locally, execute the following commands:

$ composer require --dev hoa/devtools
$ vendor/bin/hoa devtools:documentation --open

More documentation can be found on the project's website: hoa-project.net.

Getting help

There are mainly two ways to get help:

Contribution

Do you want to contribute? Thanks! A detailed contributor guide explains everything you need to know.

License

Hoa is under the New BSD License (BSD-3-Clause). Please, see LICENSE for details.

Related projects

The following projects are using this library:

ruler's People

Contributors

arnegroskurth avatar bhoat avatar grummfy avatar hywan avatar jroenf avatar k-phoen avatar marclaporte avatar mnapoli avatar pierozi avatar samundra avatar shouze avatar shulard avatar silverfire avatar stephpy avatar vonglasow avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ruler's Issues

How could I combine different existing rules?

Hi,

I am new to Ruler, and just have a basic question: How could I combine different existing rules dynamically?
Tried to google and searched in the GIT repo, did not find any related things...

Thanks!

Asserter get all operators

I would like a function to get all operators (closures) that are defined for a ruler. So far only a getOperator method is available. Which makes it kind of a blackbox if you don't know the operator name up front.

avoid use of serialize

Hello,
in the documentation of hoa ruler we find this

// We serialize and save the object model.
$database->save($ruleId, serialize($model));

But we should avoid serialize for long storage. Why, because serialize lead on php model, and this model can evolve with PHP imself. So a value in the database can become invalid because of the php version have change. But there is also another issue. If you have an evolution of hoa\ruler, it's possible that the serialisation of the object differ from one version to another.

We can imagine use json or other serialisation model.

How can I check that array contain sub array?

Hi,
Need check that some array contain subarray, only way I find create rule for all items in subarray, like "item1 in ['item1', 'item2', 'item3', 'item4', 'item5'] and item2 in ['item1', 'item2', 'item3', 'item4', 'item5'] and item5 in ['item1', 'item2', 'item3', 'item4', 'item5']"
Thanks!

defaultAsserter as static leading to problems??

Hi,

I'm running two tests. Each test has it's own Ruler, but i think that both rulers share the same Asserter because it is declared as static in the ruler class. This leads to operators being available in the second ruler which where defined in the first ruler.

A workaround would be to use the new getOperators() method to get all the operators. And then call setOperator with value null on them. But this is not so nice to do it this way.

Can the ruler output stand-alone PHP?

Taken from the readme

$rule  = 'group in ("customer", "guest") and points > 30';

There is an example of how to convert it to PHP code by calling Hoa\Ruler\Ruler::interprete. But this return \Hoa\Ruler\Model objects. Is it also possible generate stand-alone PHP code?

Getting a closure like this

<?php
$closure = function($group, $points) {
    return in_array($group, ['customer', 'guest']) AND $points > 30;
}

would be great to reuse the code in projects and test it with PHP directly.

Perhaps it can be done with using the Hoa compiler ??

Why operator cast to lowercase ?

Hello,

I trying a rule with camelcase operator and discover the Ruler\Model cast to lowercase.
Business logic term may want use native language of business with long name like :

produitBloquerEnRetail(product) vs produitbloquerenretail(product)
produitAlternatifEnStock(product) vs produitalternatifenstock(product)

https://github.com/hoaproject/Ruler/blob/master/Model/Model.php#L140

return new Operator(mb_strtolower($name), $arguments, $isFunction);

Easy resolve by casting rule to lower, but if operator are cast, why not rule ?

Ruler hackbook

When would the hackbook /documentation be available for HOA/Ruler?

Match any in array?

I have a quick question. I have a context value that is an array, and I'd like to see if any of the values within the array match the rule. For example:

$rule = 'segment = "test"';

$context            = new Hoa\Ruler\Context();
$context['segment'] = array('value1','test','value3');

I know I can use:

$rule = 'segment[1] = "test"';

To get a match, but I don't know if the "test" value will be in the array, or what key it might be at. Is there a way to check to see if any of the values within the array match?

Thanks!

Evaluate rule after a given time

Hello again.

Another question / proposal:

I'm trying to solve how to assess a rule with a timing condition (Eg value > 21 and elapsed_time > 300)

When the other conditions are true, after 300 seconds I want to check if the context remain the same. It's easy search in de DB to find the received values in the time range, but I don't know how to postpone the evaluation without blocking the whole process.

I saw that the Worker library could serve my purpose, but it seems this is overengineering.

Is there any way to do what I need with Ruler?

Any clue would be greatly appreciated.

Thanks in advance.

add custom alias to operators

Hello,
is it possible to add custom alias to the operators and/or for example ?
I'd like to translate them in french, so "and" would be aliased by "et" and "or" by "ou".

Thanks for this awesome piece of software. :)

"in" operator problem

Hello,
for a rule like $rule = 'group in ("customer")' an exception is thrown
i don't know if bug or intended.

It appears the "in" doesn't work with a single value only with two or more

Interpreted rule should be available in JSON format

serialize(
Hoa\Ruler::interprete(
'logged(user) and group in ("customer", "guest") and points > 30'
)

This might be good for storing in a relational db, but for those of us using NoSql db like Mongo, It would be nice to get a JSON encoded rule model ...

New added operators list

It would be nice if we had a list of new added operators if is the case

For example for the rule :

$rule = 'isConnection(user1,user2) and group in ("customer", "guest") and points > 30';

will return something like

array(
'operator' => isConnection,
'context' => array ('user1', 'user2')
)

Matching values in an Array?

Hi,

I need to match values in an array. For example:

$rule = "role in ["admin","user","employee']";

$context['role'] = array(''employee","superadmin").

If any of the role given as an array present in the rule then it should return true. Could anyone please help me how can I achieve this?

Maybe an issue in `identifier` definition ?

Hello,

I'm currently using Hoa\Ruler in a project to validate complex form fields rules.

I use UUID v4 as field name so I can have a rule like this :

"baacf872-d554-4125-95e2-b16cdf3dd8f3 = "GIST" or 4e213f7d-7be9-45c5-bb8c-0431351bd0dd = GIST"

This rules doesn't compile properly because the second field identifier start with an integer so the analyzed token sequence is :

  • identifier : baacf872-d554-4125-95e2-b16cdf3dd8f3
  • operator : =
  • string = "GIST"
  • operator : or
  • integer: 4
  • indentifier : e213f7d-7be9-45c5-bb8c-0431351bd0dd
  • operator : =
  • string = "GIST"

I don't know if it's a real issue because in programming languages a variable name shouldn't start with an integer but I still prefer create an issue to be sure 😄.

I've bypassed my problem by using an array as my primary variable :

fields['baacf872-d554-4125-95e2-b16cdf3dd8f3'] = "GIST" or fields['4e213f7d-7be9-45c5-bb8c-0431351bd0dd'] = "GIST"

Provide array and object access

It would be great to see array and object access in rules:

Array access:

$rule = 'user[age] > 18';
$context['user'] = array('age' => 25);

Object access:

$rule = 'user.age > 18;

class User {
    public $age = 32;
}

$context['user'] = new User();

Add error message when assert is false

Take an example

// The User object.
class User {

    const DISCONNECTED = 0;
    const CONNECTED    = 1;

    public $group      = 'customer';
    public $points     = 42;
    protected $_status = 0;

    public function getStatus ( ) {

        return $this->_status;
    }
}

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

// New rule.
$rule  = 'logged(user) and group in ["customer", "guest"] and points > 30';

// New context.
$context         = new Hoa\Ruler\Context();
$context['user'] = function ( ) use ( $context ) {

    $user              = new User();
    $context['group']  = $user->group;
    $context['points'] = $user->points;

    return $user;
};

// We add the logged() operator.
$ruler->getDefaultAsserter()->setOperator('logged', function ( User $user ) {

    return $user::CONNECTED === $user->getStatus();
});

// Finally, we assert the rule.
var_dump(
    $ruler->assert($rule, $context)
);

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

Ok so we have here a rule not respected, but why this rule doesn't passed is it because we have the user logout or because them points is <= 30
It could be useful to identify the reason of breaking rules. But all rules depends on operator so I think the operator could provide a property with error message when the result of validation is false and aggregate in ruler to got it.

To continue the example above

var_dump(
    $ruler->getErrorMessages();
);
/**
 * Will output:
 *     array(
 *         'User is not logged'
 *     )
 */

What do you think ?

Invert operator

Hello, is it possible to do something like "human" not in ["alien", "animals"] ?

Disassembler: parentheses

Hi, the disassembler does not produce the original rule back in some cases. But adds unnecessary parentheses. Consider the following code:

<?php
$hoaDisassembler = new Hoa\Ruler\Visitor\Disassembly();
$model = Ruler::interprete('2 = 3');
$rule = $hoaDisassembler->visit($model);
echo $rule;

This outputs (2 = 3) where the 2 = 3 without parentheses should be sufficient (as the original rule).

Access to an array returned by a method property

Hi. This way of accessing is not working, almost in my tests.

Imagine that PropertyA is an associative array like this: ["a" => "b", "c" => "d"]

assertion) object.getPropertyA()["a"] = "b" --> Throws the following error

CRITICAL: Uncaught PHP Exception Hoa\Ruler\Exception\Asserter: "Try to access to an undefined index: a (dimension number 2 of object), because it is not an array." at /var/www/html/vendor/hoa/ruler/Visitor/Asserter.php line 345 {"exception":"[object] (Hoa\\Ruler\\Exc
eption\\Asserter(code: 3): Try to access to an undefined index: a (dimension number 2 of object), because it is not an array. at /var/www/html/vendor/hoa/ruler/Visitor/Asserter.php:345)"

It seems that the chain object.property()[array_position] does not work.

----------Update------------------------

I just realized that the array is not a basic array. It is a Doctrine ArrayCollection

i.e.) new ArrayCollection(["a" => "b", "c" => "d"]

Maybe it explains the problem.

Lint

Hi,

I would really like to be able to lint an expression.
Ruler::interpret() seems like a candidate, but doesn't really cut it.

Something like Ruler::lint($rule):bool would be enough for my purposes. A ::getLintErrors() is optional and could be implemented later.
Examples:
lint('points > 40') > true
lint('points && true') > false

Could something like this be done?
I am also willing to build it myself, if anyone can provide me with some guidance.

Incorrect condition on disassembly

Hi all,

There is a problem in Disassembly class on the isToken condition.
Instead of adding parenthesis when using not, and, or, xor , it adds parenthesis on other operators.
Example:

  • the rule I write and I expect to get after interpreting it, getting the Model and then disassembling it:
((age > 50 and civility = 'mr') or (age < 40 and civility = 'mrs'))
  • the rule I currently get with hoa/ruler in version 1.15.11.09 :
(age > 50) and (civility = 'mr') or (age < 40) and (civility = 'mrs')

I guess that the isToken condition , instead of being compared to false, should be compared to true.
Modifying this condition this way, I get the expected result.

Thanks in advance :-)

"in" operator fails when array contains a single item

Suppose you have a rule:

$context['var'] = "a";
$rule = 'var in ("a","b")';
$ruler->assert($rule,$context);

This evaluates to true, which is correct.

However if your array has a single item:

$context['var'] = "a";
$rule = 'var in ("a")';
$ruler->assert($rule,$context);

You receive an fatal error:

 PHP Catchable fatal error:  Argument 2 passed to Hoa\Ruler\Visitor\Asserter::Hoa\Ruler\Visitor\{closure}() must be of the type array, string given in /hoa/ruler/Hoa/Ruler/Visitor/Asserter.php on line 112"

syntax error in a core/Consistency.php after installing with composer

Hi.
I'm a newbie of composer so probably i made something wrong but after installing with composer , netbeans warned me about an error in core/Consistency.php

that is because of

if (!function_exists('curry')) {
function curry($callable)
{
$arguments = func_get_args();
array_shift($arguments);
$ii = array_keys($arguments, …, true);

    return function () use ($callable, $arguments, $ii) {
        return call_user_func_array(
            $callable,
            array_replace($arguments, array_combine($ii, func_get_args()))
        );
    };
}

}

the problem is the "..." of
$ii = array_keys($arguments, …, true);

Add an error message when we set a keyword in the context

There is no warning if we set a reserved keyword in the context.
But we can not use it because there is no variable recognition (by escaped field or by location).

<?php

require __DIR__ . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php';

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

// New rule.
$rule  = 'points > 30 and and=1';

// New context.
$context         = new Hoa\Ruler\Context();
$context['points'] = 42;
$context['and'] = 1;

// Finally, we assert the rule.
var_dump(
    $ruler->assert($rule, $context)
);

/**
 * Will output:
 *     bool(true)
 */
PHP Fatal error:  Uncaught Hoa\Compiler\Llk\Parser::parse(): (0) Unexpected token ">" (identifier) at line 1 and column 8:
points > 30 and and=1
       ↑
in /Users/agerlier/Documents/repogit/Ruler/vendor/hoa/compiler/Llk/Parser.php at line 1.
  thrown in /Users/agerlier/Documents/repogit/Ruler/vendor/hoa/compiler/Llk/Parser.php on line 1

Fatal error: Uncaught Hoa\Compiler\Llk\Parser::parse(): (0) Unexpected token ">" (identifier) at line 1 and column 8:
points > 30 and and=1
       ↑
in /Users/agerlier/Documents/repogit/Ruler/vendor/hoa/compiler/Llk/Parser.php at line 1.
  thrown in /Users/agerlier/Documents/repogit/Ruler/vendor/hoa/compiler/Llk/Parser.php on line 1

Well, at the first run we know the error.

Bug or not bug?

If the rule is editable by an end-user, Hoa\Ruler\Ruler::interpret($rule) should point on the second and instead of >.

Fatal error on 'true' rule

Given the following code:

$ruler = new \Hoa\Ruler\Ruler();
$result = $ruler->assert('true', new \Hoa\Ruler\Context());

I expect $result to equal boolean(true), but instead I get a Fatal error.
When I change the rule to 'true = true', I get the expected result.

But the main point is that I am getting a (non catchable) fatal error from which I cannot recover. A fatal error is unacceptable to me in this context. I should always be able to recover.

Error "Failed to open stream hoa://Library/Ruler/Grammar.pp" wich seems related to case sensitivity

Hi,

I'm trying to use Ruler in my project, but when i "interpret" a rule, i get the following error :
Failed to open stream hoa://Library/Ruler/Grammar.pp

After some debugging i found that Hoa\Protocol\Protocol is looking into two directories (at the end of the "resolve" function) :
<my_root>/src/vendor/Hoathis/Ruler/Grammar.pp
<my_root>/src/vendor/Hoa/Ruler/Grammar.pp

But the file_exists that comes after always returns false because composer downloaded the files in <my_root>/src/vendor/hoa/ruler/Grammar.pp
The directories hoa and ruler are lowercase

Am i missing something ?
Thank you

Could identifiers start with a digit?

Bonjour,

Pour faire suite à cette discussions sur la mailing list support d'Hoa => http://lists.hoa-project.net/arc/support/2014-11/msg00004.html, je pense que la grammaire de reconnaissance des identifiants pour l'interprétation des règles peut être améliorée.

En effet, dans le cas d'identifiants commençant par un chiffre comme 1_identifiant, le premier caractère est reconnu comme un entier et le reste génère une erreur au lieu de considérer l'ensemble comme un identifiant.

J'ai trouvé une grammaire alternative qui fonctionne sans casser la détection des entiers (a vérifier sur des jeux de test plus complet cependant) qui est la suivante => \d+(?![^\s()[],.])

Qu'en pensez-vous ?

Add native arithmetical expressions support

A trivial merge of Hoa\Math in this library to get native arithmetical expressions support.

It will introduce a change (so a break) in the grammar. Array declarations will be [1, 2, 3] and no longer (1, 2, 3). The library has the RC state, so we can still introduce BC breaks. I particulary ask @stephpy, @shouze and @osaris to get their feedbacks :-).

Note: we should not add new features in the grammar in the future. If we want more power, we should focus on Hoathis\Lua which is a much better scripting language (imperative, procedural, functional etc.).

Wrong use of instanceof

Hi,
I have spotted a wrong use of instanceof having anyway the expected result.
Have a look at Disassembly class :

if ($element instanceof Ruler\Model) {

should fail because expected class is Ruler\Model\Model , BUT it just works.
I didn't know that instanceof works with upper level in namespace. In fact it does not, it's just because last level in checked namespace matches the classname.

To test it:

        $model = new Hoa\Ruler\Model\Model();
        var_dump($model instanceof Hoa);
        var_dump($model instanceof Hoa\Ruler);
        var_dump($model instanceof Hoa\Ruler\Model);
        var_dump($model instanceof Hoa\Ruler\Model\Model);
        $operator = new Hoa\Ruler\Model\Operator('and');
        var_dump($operator instanceof Hoa);
        var_dump($operator instanceof Hoa\Ruler);
        var_dump($operator instanceof Hoa\Ruler\Model);
        var_dump($operator instanceof Hoa\Ruler\Model\Operator);

results in:

bool(false)
bool(false)
bool(true)
bool(true)
bool(false)
bool(false)
bool(false)
bool(true)

FYI, we're using PHP 5.5.9-1ubuntu4.14 .
I guess that the condition I was talking about should be:

if ($element instanceof Ruler\Model\Model) {

Thanks in advance!

Date rules

Hi,

I'm playing around with this fantastic library and wondered what would be the best way to make rules with dates.

If you could include some examples in the documentation it would be great.

Thank you.

add ability to pass operator via context

It would be fantastic if i could pass the operator in via the context like the following example

    $ruleString = 'app operator value';

    $context = new Context();
    $context['app'] = $this->critera['app'];
    $context['operator'] = '!=';
    $context['value'] = $rule->value;

Introduce lazy evaluation in functions/operators

For example, if I have a rule

$rule  = 'group in ("customer", "guest") and points > 30';

and group is not customer neither guest we don't need to evaluate points > 30 and directly return false.

Interface for writing rules

Hi and thanks for the awesome package!

We're looking to build a front-end interface for writing rules and wondering if there were any recommendations for autocompleting / suggesting rule operators?

Disassembler buggy

I tested the disassembler (Hoa\Ruler\Visitor\Disassembly) for a rule like "true or false", this works. But when i test this rule it doesn't work:

<?php
$hoaDisassembler = new Hoa\Ruler\Visitor\Disassembly();
$model = Ruler::interprete('not true');
$rule = $hoaDisassembler->visit($model);
var_dump($rule);

/*
 * Stacktrace:
 * vendor\hoa\ruler\Hoa\Ruler\Visitor\Disassembly.php:92
 * vendor\hoa\ruler\Hoa\Ruler\Model\Operator.php:225
 * vendor\hoa\ruler\Hoa\Ruler\Visitor\Disassembly.php:78
 * Call from my code here ..
 */

Error: Undefined offset: 1

Generating invalid PHP code with Hoa\Ruler\Visitor\Compiler::visit

Hi,

If we have the following rule:

user.logged() = true and points > 30

then when we try to see the PHP code needed to create the model, we will get a PHP code that has syntax errors.

echo Hoa\Ruler\Ruler::interprete(
    'user.logged() = true and points > 30'
);

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

You can see that right after logged there is a comma and then a closed parenthesis. This leads to a syntax error.
This happens when a method is called with no arguments. The logged method has no arguments.

Chain after a function call

Hello,
it's not possible to chain after a function call.
For example if I define a variable in the context which return an object, I'm unable to access the object's properties / functions.
This exception is thrown :

[Hoa\Compiler\Exception\UnexpectedToken]                           
Unexpected token "." (dot) at line 1 and column 46:

Produce results in response of rule evaluations

Ruler is well designed for writing rules and making assertions on them (i.e checking if some input validates the rule).

An nice feature would to be able to produce any result in response of these assertions (i.e if some input validates a rule then the engine should provide us a value).

Let's take a simple example:

  • If I'm at least 16 years old, I can buy beer
  • If I'm at least 18 years old, I can buy whiskey, gin or vodka

With only ruler, I would have to write two rules:

  • $r1 = 'person.age >= 16'
  • $r2 = 'person.age >= 18'

And some PHP code:

<?php

$ruler = new Hoa\Ruler\Ruler();
$context = new Hoa\Ruler\Context();
$context['person'] = new Person();
$context['person']->age = 19;

$beverages = [];

if ($ruler->assert($r1, $context)) {
    $beverages[] = 'beer';
}

if ($ruler->assert($r2, $context)) {
    $beverages[] = 'whiskey';
    $beverages[] = 'gin';
    $beverages[] = 'vodka';
}

var_dump($beverages); 

/*
array(4) {
  [0] => string(3) "beer",
  [1] => string(3) "whiskey",
  [2] => string(3) "gin",
  [3] => string(3) "vodka"
}
*/

This is not really efficient: the more rules I have, the more code I need to write. If the results change, I'll have to change my PHP code, ...

What I propose here is something more flexible:

$ruler = new Hoa\Ruler\Ruler();
$rules = new Hoa\Ruler\Rules();
$context = new Hoa\Ruler\Context();
$context['person'] = new Person();
$context['person']->age = 19;

$rules->add('16yo', new Hoa\Ruler\Rules\ThenElse('person.age >= 16', ['beer'], ['water']), 1);
$rules->add('18yo', new Hoa\Ruler\Rules\Then('person.age >= 18', ['whiskey', 'gin', 'vodka']), 2);

var_dump($rules->getBestResult($ruler, $context));
/*
array(3) {
  [0] => string(3) "whiskey",
  [1] => string(3) "gin",
  [2] => string(3) "vodka"
}
*/

var_dump($rules->getAllResults($ruler, $context));
/*
array(2) {
  '18yo' => array(3) {
    [0] => string(3) "whiskey",
    [1] => string(3) "gin",
    [2] => string(3) "vodka"
  },
  '16yo' => array(1) {
    [0] => string(3) "beer"
  }
}
*/

Let me explain:

  • Hoa\Ruler\Rules is a collection of rules, ordered by a priority,
  • it has a add(string $id, Rule $rule, int $priority) method,
  • the $id lets us reuse the rule result in following rules,
  • the $rule is a standard ruler rule (a string or a compiled rule) wrapped in a Then or ThenElse class,
  • the $priority is used to order the collection (higher priority will run first);
  • we can ask for the best results (getBestResult) which will be the result of the higher priority rule validating the context,
  • we can ask for all results (getAllResults) of valid rules

This kind of feature will allow for more generic code when it comes to work with rules and produce results depending on them.

The Rules collection can easily be built with results coming from a database.

What has to be done:

  • Implement the Rules collection (first with an SplPriorityQueue and then with the hoa/heap library)
  • Implement the Then rule class (it returns a result when the rule validates the input)
  • Implement the ThenElse rule class (it returns a result when the rule validates the input and another when the rule does not validate the input)
  • Implement an algorithm to resolve rules dependencies (indeed, a rule can depend on another one). hoa/graph could be a good candidate for this kind of work.
  • Implement a rule() function to reference the result of one rule from another rule

Let me know if I forgot something. I have a working POC which needs some cleanup.

Matches operator does not match

I really love this project, it's working great so far! Except one thing, which I'm probably failing to understand (correctly).
I'm trying to match a 'description' with a partial string. I've found the matches operator and created the following rule:
description matches "somestring"

I load up the context, the rule, etc. And run it against this context:
lorem ipsum dolor sit somestring amet

Now I'm expecting this to return true (as 'somestring' matches a part of the context. Yet it returns false. I was expecting a similar functionality like the strpos function in PHP.

Am I using the operator wrong, or do I have to define a custom operator to accomplish something like this?

Thanks in advance.

To add a bit more context (hehe), this is the code that's supposed to run all the rules. If a rule matches a transaction. a category_id is added to the transaction.

$rules = Rule::all();
$transactions = Transaction::all();
foreach ($transactions as $transaction) {
    $context = new Context();
    $context['account_id'] = $transaction->account_id;
    $context['account_number'] = $transaction->account_number;
    $context['account_name'] = $transaction->account_name;
    $context['type'] = $transaction->type;
    $context['amount'] = $transaction->amount;
    $context['description'] = $transaction->description;
    $context['date'] = $transaction->date;
    $context['book_code'] = $transaction->book_code;

    $hoaRuler = new Ruler();

    foreach ($rules as $rule) {
        if ($hoaRuler->assert(strtolower($rule->rule), $context)) {
            $transaction->category_id = $rule->category_id;
            $transaction->save();
        }
    }
}

Precedence of logical operators

Hello,
thank you very much for this wonderful library.

I think I found a bug, where the well-known precedence of logical operators isn't applied.

The following three expressions which are semantically identical yield different results:

The explicit way:
(false and true) or true correctly results in true

The implicit way which is idencal due to mathematical conventions:
false and true or true falsely returns false!

The implice way, but vice-versa:
true or true and false correctly results in true again, presumably due to LAZY_BREAK feature.

Maybe I can later provide a failing unit test if desired. Meanwhile, here is my "test suite":

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

        foreach (['true', 'false'] as $a) {
            foreach (['true', 'false'] as $b) {
                foreach (['true', 'false'] as $c) {

                    $rule1 = "($a and $b) or $c";
                    $rule2 = "$a and $b or $c";
                    $rule3 = "$c or $b and $a";

                    var_dump(
                        $rule1 . intval($hoaRuler->assert($rule1)),
                        $rule2 . intval($hoaRuler->assert($rule2)),
                        $rule3 . intval($hoaRuler->assert($rule3))
                    );
                }
            }
        }

With $a = false, $b = true and $c = false and
with $a = false, $b = false and $c = true the described behaviour can be observed.

Thanks for your attention.

Droping PHP version support

Hello,

I don't expect this to be fixed, but wanted to sound the horn that dropping support for PHP 5.3 in a revision version (least significant number) creates a lot of problems for downstream users like us.
Ideally this change would have been signaled, to the least, with a minor version bump.

Thanks,
Razvan

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.