bobthecow / Ruler

A simple stateless production rules engine for modern PHP
MIT License
1.06k stars 140 forks source link

Allow Evaluation of Rules in RuleSet #35

Closed jaimz22 closed 6 years ago

jaimz22 commented 10 years ago

I needed to simply evaluate the Rules in a RuleSet to see if it was a simple pass or fail for all the Rules in the RuleSet as an atomic group.

Others might find this handy as well considering the idea that you might have a RuleSet that has no actions.

Additionally I added a fix for my issue #36

bobthecow commented 10 years ago

Meaning, you want to treat all rules in a RuleSet as if they're ANDed together? Any reason why you couldn't just add them to a logical AND rule?

jaimz22 commented 10 years ago

Yes, and no! While the same idea could be achieved by using a LogicalAnd operator. The issue for me arose when I could not add a rule to an already existing LogicalAnd operator.

$notJon = $rb->create($rb['userName']->noEqualTo('jwage'));
$notJustin = $rb->create($rb['userName']->notEqualTo('bobthecow'));
$notTheDude = $rb->create($rb['userName']->notEqualTo('lebowski'));

$rule = new LogicalAnd($notJon,$notJustin,$notTheDude);
// imaginary logic and other code goes here... for couple dozen lines of code.
// maybe we even past the 'set' (held in the LogicalAnd) to another class method
// now we need to add this rule because of some logic says so....
$notJames = $rb->create($rb['userName']->notEqualTo('jaimz22'));

$rule->evaluate($context)

This allows someone to dynamically build up a set of rules to be tested against, then evaluate them all... imagine you have 30 rules to match a certain criteria built up through multiple different classes and bits of logic. I suppose you could gather them all in an array but even then you couldn't simply plop that array into a LogicalAnd operator, you'd have to use call_user_func_array or something of that nature.

This could also be modified to allow the rules to be evaluated in a mutually exclusive fashion by simply adding array keys to the rules in the set, then returning an array of keys with true or false as the value (in other words, naming the Rules in the RuleSet) Doing this would also possibly satisfy issue #9 as well.

bobthecow commented 10 years ago

I suppose you could gather them all in an array but even then you couldn't simply plop that array into a LogicalAnd operator, you'd have to use call_user_func_array or something of that nature.

But you can :) Logical operators take an array of propositions, it's only the RuleBuilder helper that uses a variadic function.

jaimz22 commented 10 years ago

well... then I retract my pull request.

bobthecow commented 10 years ago

No, I'm not saying it's not a good feature, I'm just providing a counterpoint :)

jaimz22 commented 10 years ago

hah ok. Well I'd like to point out that my pull request build is failing because of the edit I made for issue #36 where it's trying to convert Set to an int... I have no clue why that's happening.

I believe that adding RuleSet->evaluate() provides a spot for future extensiblity. As I previously mentioned, you could allow people to provide names for their rules, then have the evaluate() method return an array of ['ruleName'=>true/false]. Naming rules would also allow you to remove rules from a set.

bobthecow commented 10 years ago

The test is failing because this array:

[1, 2, ['foo']]

contains this value:

['foo']

… and apparently SORT_REGULAR can't deal with that :-/

jaimz22 commented 10 years ago

I do have a solution, move the array_unique to above the foreach that creates the Set objects. I updated my code

bobthecow commented 10 years ago

What if the set contains other things that don't play nice with SORT_REGULAR? Would it be worth coming up with a more robust solution that checks spl_object_hash or json_encode or something for objects?

jaimz22 commented 10 years ago

well there is another very simple way to handle it. simply creating an md5 of serialized values...

$uniques = array();
foreach ($this->value as $value) {
    $hash = md5(serialize($value));
    $uniques[$hash] = $value;
}
$this->value = array_values($uniques);

However, to prevent the in_array call in the setContains() method from freaking out, you'd have to add the third parameter for in_array() which is $strict (doesn't cast types) but then you're left with the problem that when comparing objects, they must literally be the same object (same memory address) if you don't add this third parameter, php will try to cast to whatever type it's testing against.

The other route is to not use in_array in the setContains() method and iterate everything in a loop!

jaimz22 commented 10 years ago

and apparently SORT_REGULAR can't deal with that :-/

SORT_REGULAR does deal with that just fine, the actual error is in fact the call to in_array() that's trying to cast the object...