一个简单的无状态生产规则引擎,适用于现代 PHP
... provided by the RuleBuilder:
$rb = new RuleBuilder;
$rule = $rb->create(
$rb->logicalAnd(
$rb['minNumPeople']->lessThanOrEqualTo($rb['actualNumPeople']),
$rb['maxNumPeople']->greaterThanOrEqualTo($rb['actualNumPeople'])
),
function() {
echo 'YAY!';
}
);
$context = new Context([
'minNumPeople' => 5,
'maxNumPeople' => 25,
'actualNumPeople' => fn() => 6,
]);
$rule->execute($context); // "Yay!"... you can use it without a RuleBuilder:
$actualNumPeople = new Variable('actualNumPeople');
$rule = new Rule(
new Operator\LogicalAnd([
new Operator\LessThanOrEqualTo(new Variable('minNumPeople'), $actualNumPeople),
new Operator\GreaterThanOrEqualTo(new Variable('maxNumPeople'), $actualNumPeople)
]),
function() {
echo 'YAY!';
}
);
$context = new Context([
'minNumPeople' => 5,
'maxNumPeople' => 25,
'actualNumPeople' => fn() => 6,
]);
$rule->execute($context); // "Yay!"But that doesn't sound too fun, does it?
……$e = $rb['e']; // These should both be arrays
$f = $rb['f'];
// Manipulate sets with set operators
$e->union($f);
$e->intersect($f);
$e->complement($f);
$e->symmetricDifference($f);
$e->min();
$e->max();
// And use set Propositions to include them in Rules.
$e->containsSubset($f);
$e->doesNotContainSubset($f);
$e->setContains($a);
$e->setDoesNotContain($a);…$rb->logicalNot($aEqualsB); // The same as $aDoesNotEqualB :)
$rb->logicalAnd($aEqualsB, $aDoesNotEqualB); // True if both conditions are true
$rb->logicalOr($aEqualsB, $aDoesNotEqualB); // True if either condition is true
$rb->logicalXor($aEqualsB, $aDoesNotEqualB); // True if only one condition is trueevaluate and execute Rulesevaluate() a Rule with Context to figure out whether it is true.
$context = new Context([
'userName' => fn() => $_SESSION['userName'] ?? null,
]);
$userIsLoggedIn = $rb->create($rb['userName']->notEqualTo(null));
if ($userIsLoggedIn->evaluate($context)) {
// Do something special for logged in users!
}If a Rule has an action, you can execute() it directly and save yourself a
couple of lines of code.
$hiJustin = $rb->create(
$rb['userName']->equalTo('bobthecow'),
function() {
echo "Hi, Justin!";
}
);
$hiJustin->execute($context); // "Hi, Justin!"execute a whole grip of Rules at once…Several of our examples above use static values for the context Variables. While that's good for examples, it's not as useful in the Real World. You'll probably want to evaluate Rules based on all sorts of things...
You can think of the Context as a ViewModel for Rule evaluation. You provide the static values, or even code for lazily evaluating the Variables needed by your Rules.
…Now you have all the information you need to make Rules based on Order count or the current User, or any number of other crazy things. I dunno, maybe this is for a shipping price calculator?
If the current User has placed 5 or more orders, but isn't "really annoying", give 'em free shipping.
$rb->create(
$rb->logicalAnd(
$rb['orderCount']->greaterThanOrEqualTo(5),
$rb['reallyAnnoyingUsers']->doesNotContain($rb['userName'])
),
function() use ($shipManager, $context) {
$shipManager->giveFreeShippingTo($context['user']);
}
);As an added bonus, Ruler lets you access properties, methods and offsets on your Context Variable values. This can come in really handy.
Say we wanted to log the current user's name if they are an administrator:
…That was a bit of a mouthful. Instead of creating context Variables for everything we might need to access in a rule, we can use VariableProperties, and their convenient RuleBuilder interface:
// We can skip over the Context Variable building above. We'll simply set our,
// default roles on the VariableProperty itself, then go right to writing rules:
$rb['user']['roles'] = ['anonymous'];
$rb->create(
$rb->logicalAnd(
$userIsLoggedIn,
$rb['user']['roles']->contains('admin')
),
function() use ($context, $logger) {
$logger->info(sprintf("Admin user %s did a thing!", $context['user']['fullName']);
}
);If the parent Variable resolves to an object, and this VariableProperty name is "bar", it will do a prioritized lookup for:
barbarbarIf the Variable resolves to an array it will return:
barIf none of the above are true, it will return the default value for this VariableProperty.
If none of the default Ruler Operators fit your needs, you can write your own! Just define additional operators like this:
namespace My\Ruler\Operators;
use Ruler\Context;
use Ruler\Operator\VariableOperator;
use Ruler\Proposition;
use Ruler\Value;
class ALotGreaterThan extends VariableOperator implements Proposition
{
public function evaluate(Context $context): bool
{
list($left, $right) = $this->getOperands();
$value = $right->prepareValue($context)->getValue() * 10;
return $left->prepareValue($context)->greaterThan(new Value($value));
}
protected function getOperandCardinality()
{
return static::BINARY;
}
}Then you can use them with RuleBuilder like this:
$rb->registerOperatorNamespace('My\Ruler\Operators');
$rb->create(
$rb['a']->aLotGreaterThan(10);
);Check out the test suite for more examples (and some hot CS 320 combinatorial logic action).
Ruler doesn't bother itself with where Rules come from. Maybe you have a RuleManager wrapped around an ORM or ODM. Perhaps you write a simple DSL and parse static files.
Whatever your flavor, Ruler will handle the logic.
暂无开放 Issues,或尚未同步最近议题。