Installation:
composer require drinks-it/rule-engine
For full dependency support (Doctrine, Symfony):
composer require drinks-it/rule-engine --with-dependencies
Bundle Registration:
Add to config/bundles.php:
DrinksIt\RuleEngineBundle\RuleEngineBundle::class => ['all' => true]
Doctrine DBAL Configuration (if using Doctrine):
# config/packages/doctrine.yaml
doctrine:
dbal:
types:
rule-engine-conditions: DrinksIt\RuleEngineBundle\Doctrine\Types\ConditionsType
rule-engine-action: DrinksIt\RuleEngineBundle\Doctrine\Types\ActionType
rule-engine-event: DrinksIt\RuleEngineBundle\Doctrine\Types\TriggerEventType
Generate Rule Engine Entity:
php bin/console make:rule-engine MyRuleEngine
php bin/console make:migration
php bin/console doctrine:migrations:migrate
use DrinksIt\RuleEngineBundle\RuleEngine\RuleEngineInterface;
// In a controller/service
$ruleEngine = $container->get(RuleEngineInterface::class);
$context = ['user_age' => 25, 'is_premium' => true];
// Evaluate a rule (assuming you've created one via the CLI)
$ruleEngine->evaluate('discount_rule', $context);
Define Rules via CLI:
php bin/console make:rule-engine MyDiscountRule
This generates a CRUD interface for managing rules in the admin panel.
Rule Structure:
{"user_age": {"operator": ">", "value": 18}}).{"apply_discount": {"value": 10}}).user_checkout).Integration with Business Logic:
// Trigger an event (e.g., after user checkout)
$eventDispatcher = $container->get('event_dispatcher');
$eventDispatcher->dispatch(new UserCheckoutEvent($user));
// Or manually evaluate rules
$ruleEngine->evaluate('discount_rule', ['user' => $user]);
Context Passing: Always pass a structured array/object as context to rules. Example:
$context = [
'user' => $userEntity,
'cart' => $cart,
'request' => $request->toArray(),
];
Dynamic Rule Loading: Fetch rules from the database dynamically:
$rules = $ruleEngine->findRulesByEvent('user_checkout');
foreach ($rules as $rule) {
$ruleEngine->evaluate($rule->getName(), $context);
}
Action Handling: Register custom actions via services:
# config/services.yaml
services:
App\Service\CustomActionHandler:
tags: [drinks_it.rule_engine.action_handler]
Event-Driven Architecture: Use Symfony’s event system to trigger rules:
// In an event subscriber
public function onUserCheckout(UserCheckoutEvent $event) {
$ruleEngine->evaluate('post_checkout_rules', ['user' => $event->getUser()]);
}
Doctrine Type Mismatches:
rule-engine-* types are registered before migrations run. Otherwise, you may encounter:
Unknown Doctrine type [rule-engine-conditions]
php bin/console doctrine:schema:update --force after adding types.Context Data Serialization:
__toArray() methods:
$context['user'] = $user->toArray(); // Assuming a toArray() method exists
Rule Naming Collisions:
marketing_discount_rule).Action Execution Order:
Admin Panel Dependencies:
make:rule-engine command generates a CRUD interface that relies on Symfony’s maker bundle and easy_admin.Log Rule Evaluations: Enable debug mode to log rule evaluations:
# config/packages/dev/rule_engine.yaml
drinks_it_rule_engine:
debug: true
This logs conditions, actions, and evaluation results.
Validate Rule JSON:
Use the RuleEngineBundle’s validator to check rule syntax:
$validator = $container->get('validator');
$errors = $validator->validate($ruleEntity->getConditions());
Test Rules in Isolation:
Write unit tests for rules using the RuleEngineInterface:
public function testDiscountRule() {
$ruleEngine = $this->createMock(RuleEngineInterface::class);
$ruleEngine->method('evaluate')
->with('discount_rule', ['user_age' => 25])
->willReturn(true);
$this->assertTrue($ruleEngine->evaluate('discount_rule', ['user_age' => 25]));
}
Custom Conditions/Operators: Extend the condition system by creating a custom operator:
use DrinksIt\RuleEngineBundle\RuleEngine\Condition\OperatorInterface;
class IsPremiumOperator implements OperatorInterface {
public function evaluate($value, $operatorValue) {
return $value->isPremium() === $operatorValue;
}
}
Register it in services:
services:
App\RuleEngine\IsPremiumOperator:
tags: [drinks_it.rule_engine.operator]
Custom Actions: Create reusable actions:
use DrinksIt\RuleEngineBundle\RuleEngine\Action\ActionInterface;
class SendEmailAction implements ActionInterface {
public function execute($actionData, $context) {
// Logic to send email
}
}
Tag the service:
services:
App\RuleEngine\SendEmailAction:
tags: [drinks_it.rule_engine.action_handler]
Override Default Storage: By default, rules are stored in the database. To use an alternative (e.g., Redis):
$ruleEngine = new CustomRuleEngine(
$container->get('redis'),
$container->get('validator')
);
Bind it in services.yaml:
services:
DrinksIt\RuleEngineBundle\RuleEngine\RuleEngineInterface: '@custom_rule_engine'
Event Listeners for Rules: Listen to rule evaluation events:
use DrinksIt\RuleEngineBundle\Event\RuleEvaluatedEvent;
public function onRuleEvaluated(RuleEvaluatedEvent $event) {
if (!$event->isPassed()) {
$this->logger->warning('Rule failed: ' . $event->getRuleName());
}
}
Tag the listener:
services:
App\EventListener\RuleListener:
tags: [kernel.event_listener, name: rule_evaluated]
How can I help you explore Laravel packages today?