public function testSimpleEngine() { $this->expectOutputString(implode(PHP_EOL, ['Rule 1 (Red Products) triggered for Red Bricks.', 'Rule 5 (Toys) triggered for Red Bricks.', 'Rule 3 (Green Socks) triggered for Green Soft Socks.', 'Rule 4 (Socks) triggered for Green Soft Socks.', 'Rule 6 (Clothes) triggered for Green Soft Socks.', 'Rule 4 (Socks) triggered for Yellow Sporty Socks.', 'Rule 6 (Clothes) triggered for Yellow Sporty Socks.', 'Rule 5 (Toys) triggered for Lego Blocks.', 'Rule 6 (Clothes) triggered for Black Adidas Jacket.']) . PHP_EOL); $exceptions = new Exception\ExceptionCollectorHandler(); $engine = new Engine($exceptions); $action = new Action\PrintAction(); foreach ($this->getProducts() as $product) { $context = new Context\ProductContext($product); $engine->execute($context, $this->getRules(), $action); } $this->assertSame('', implode(PHP_EOL, $exceptions->getExceptions()), 'RuleEngine should not generate errors.'); }
public function testRuleEngineSometimesFaultyAction() { $productData = ['Product 1' => [], 'Product 2' => [], 'Product 3' => []]; $rules = [new GenericRule(1, 'Always triggered', 'true')]; $expectedRules = ['Product 1' => ['Always triggered'], 'Product 2' => [], 'Product 3' => ['Always triggered']]; $expectedExceptions = ['Exception encountered while executing action ' . CallbackAction::class . ' for rule 1 (Always triggered) within ' . DynamicContext::class . ': Exception thrown for Product 2.']; $matchingRules = array_fill_keys(array_keys($productData), []); $exceptionHandler = new Exception\ExceptionCollectorHandler(); $engine = new Engine($exceptionHandler); foreach ($productData as $productName => $productValues) { $action = new CallbackAction(function ($eval, $context, RuleInterface $rule) use($productName, &$matchingRules) { if ($productName == 'Product 2') { throw new \Exception("Exception thrown for {$productName}."); } $matchingRules[$productName][] = $rule->getName(); }); $engine->execute($this->getContext($productValues), $rules, $action); } $this->assertEquals($expectedRules, $matchingRules); $errorMesgs = array_map(function (\Exception $exception) { return $exception->getMessage(); }, $exceptionHandler->getExceptions()); $this->assertEquals($expectedExceptions, $errorMesgs, 'Engine exceptions were not as expected.'); }
// Provide code compiled from rule conditions $output_generated = ''; $eval = new Rune\Util\SymfonyEvaluator(); $maxLength = 0; foreach ($rules as $rule) { $maxLength = max($maxLength, strlen($rule->getName())); } foreach ($rules as $rule) { try { $eval->setFunctions($descriptor->getFunctions()); $eval->setVariables($descriptor->getVariables()); $code = $eval->compile($rule->getCondition()); } catch (\Exception $ex) { $code = 'Compile Error (' . get_class($ex) . '): ' . $ex->getMessage(); } $output_generated .= str_pad($rule->getName(), $maxLength) . ' => ' . $code . PHP_EOL; } // Provide triggered rules and any generated errors ob_start(); foreach ($products as $product) { $context = new Context\ProductContext($product); $engine->execute($context, $rules, $action); } $output_result = htmlspecialchars(ob_get_clean(), ENT_QUOTES); $output_errors = implode(PHP_EOL, $exceptionHandler->getExceptions()); // Provide some details use for dynamic editor $json_tokens = json_encode(['constants' => [['name' => 'true', 'type' => 'boolean'], ['name' => 'false', 'type' => 'boolean'], ['name' => 'null', 'type' => 'null']], 'operators' => ['+', '-', '*', '/', '%', '**', '&', '|', '^', '==', '===', '!=', '!==', '<', '>', '<=', '>=', 'matches', 'not', '!', 'and', '&&', 'or', '||', '~', 'in', 'not in', '..', '?', '?:', ':'], 'variables' => array_values($descriptor->getVariableTypeInfo()), 'functions' => array_values($descriptor->getFunctionTypeInfo()), 'typeinfo' => $descriptor->getDetailedTypeInfo()]); $json_categories = json_encode($data['categories']); $json_products = json_encode($data['products']); $json_rules = json_encode($data['rules']); require_once 'view.php';