1. Go to this page and download the library: Download prohalexey/the-choice library. Choose the download type require.
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
prohalexey / the-choice example snippets
use TheChoice\Builder\JsonBuilder;
use TheChoice\Container;
use TheChoice\Processor\RootProcessor;
// 1. Configure contexts — map names to classes that implement ContextInterface
$container = new Container([
'visitCount' => VisitCount::class,
'hasVipStatus' => HasVipStatus::class,
'inGroup' => InGroup::class,
'withdrawalCount' => WithdrawalCount::class,
'depositCount' => DepositCount::class,
'getDepositSum' => GetDepositSum::class,
]);
// 2. Parse rules from a JSON file — returns a Root node (the rule tree)
$parser = $container->get(JsonBuilder::class);
$node = $parser->parseFile('rules/discount-rules.json');
// 3. Execute the rules
$rootProcessor = $container->get(RootProcessor::class);
$result = $rootProcessor->process($node);
use TheChoice\Engine\RuleEngine;
$engine = new RuleEngine($container);
$engine->addRule('vip_discount', $jsonBuilder->parseFile('rules/vip.json'), priority: 10);
$engine->addRule('loyal_discount', $jsonBuilder->parseFile('rules/loyal.json'), priority: 5);
$engine->addRule('fraud_block', $jsonBuilder->parseFile('rules/fraud.json'));
$report = $engine->run();
// Iterate over fired rules
foreach ($report->getFired() as $name => $ruleResult) {
echo "{$name}: {$ruleResult->result}\n";
}
// Check a specific rule
if ($report->hasFired('vip_discount')) {
$discount = $report->getResult('vip_discount')->result;
}
// Get skipped rules (result was null or false)
$skipped = $report->getSkipped();
use TheChoice\Registry\RuleRegistry;
$registry = new RuleRegistry();
$registry->register(
name: 'vip_discount',
node: $jsonBuilder->parseFile('rules/vip.json'),
tags: ['discount', 'vip'],
version: '2.1',
description: 'VIP discount: 10% of last deposit',
priority: 10,
);
// Lookup by name
$entry = $registry->get('vip_discount');
// Filter by tag
$discountRules = $registry->findByTag('discount');
// Load into the engine for batch evaluation
$engine->loadFromRegistry($registry);
$report = $engine->run();
use TheChoice\Validator\RuleValidator;
$validator = new RuleValidator(
contexts: ['withdrawalCount', 'inGroup', 'getDepositSum'],
operators: ['equal', 'arrayContain', 'greaterThan'],
);
// Validate and inspect errors
$result = $validator->validate($node);
if (!$result->isValid()) {
foreach ($result->getErrors() as $error) {
echo $error->toString();
// [root > rules > collection[0]] Context "getDisconut" is not registered (did you mean "getDepositSum"?)
}
}
// Or throw on the first invalid rule (useful in CI/CD)
$validator->validateOrThrow($node); // throws ValidationException
// Only validate operators, allow any context name
$validator = new RuleValidator(contexts: [], operators: ['equal', 'greaterThan']);
use TheChoice\Exception\ValidationException;
try {
$validator->validateOrThrow($node);
} catch (ValidationException $e) {
$result = $e->getValidationResult();
$errors = $result->getErrors(); // array<ValidationError>
echo $result->toString(); // all errors as a multi-line string
}
$trace = $rootProcessor->processWithTrace($node);
// The result is the same as $rootProcessor->process($node)
echo $trace->getValue(); // e.g. 10.5
// Human-readable explanation
echo $trace->explain();
// Root[root] → 10.5
// Condition[condition] → 10.5
// Collection[and] → TRUE
// Context[withdrawalCount equal] → TRUE
// Context[inGroup arrayContain] → TRUE
// Context[getDepositSum] → 10.5
$rootEntry = $trace->getTrace();
echo $rootEntry->getNodeType(); // "Root"
echo $rootEntry->getNodeName(); // "root"
echo $rootEntry->getResult(); // 10.5
foreach ($rootEntry->getChildren() as $child) {
echo $child->getNodeType(); // "Condition", "Collection", "Context", "Value"
echo $child->getNodeName(); // e.g. "withdrawalCount equal"
echo $child->getResult(); // the value returned by this node
// Children can be nested (e.g. Collection → Context children)
foreach ($child->getChildren() as $grandChild) {
// ...
}
}
use Symfony\Component\EventDispatcher\EventDispatcher;
use TheChoice\Engine\RuleEngine;
use TheChoice\Event\ContextEvaluatedEvent;
use TheChoice\Event\EngineRunAfterEvent;
use TheChoice\Event\RuleFiredEvent;
use TheChoice\Event\RuleErrorEvent;
$dispatcher = new EventDispatcher();
// Metrics — total run time and fired count
$dispatcher->addListener(EngineRunAfterEvent::class, function (EngineRunAfterEvent $e): void {
echo sprintf("Engine finished in %.2f ms, %d rules fired\n", $e->elapsedMs, count($e->report->getFired()));
});
// Audit — log every fired rule
$dispatcher->addListener(RuleFiredEvent::class, function (RuleFiredEvent $e): void {
echo sprintf("Rule '%s' fired with result: %s (%.2f ms)\n", $e->ruleName, var_export($e->result->result, true), $e->elapsedMs);
});
// Debug — see why a context evaluated the way it did
$dispatcher->addListener(ContextEvaluatedEvent::class, function (ContextEvaluatedEvent $e): void {
echo sprintf(" %s = %s (%s %s) → %s\n",
$e->contextName,
var_export($e->contextValue, true),
$e->operatorName ?? 'no operator',
var_export($e->operatorValue, true),
var_export($e->result, true),
);
});
// Error handling
$dispatcher->addListener(RuleErrorEvent::class, function (RuleErrorEvent $e): void {
echo sprintf("Rule '%s' failed: %s\n", $e->ruleName, $e->exception->getMessage());
});
$engine = new RuleEngine($container, $dispatcher);
$engine->addRule('vip_discount', $jsonBuilder->parseFile('rules/vip.json'), priority: 10);
$report = $engine->run();
$rootProcessor = $container->get(RootProcessor::class);
$rootProcessor->setEventDispatcher($dispatcher);
$result = $rootProcessor->process($node); // context & switch events will fire
use Psr\SimpleCache\CacheInterface;
use TheChoice\Builder\CachedJsonBuilder;
/** @var CacheInterface $cache */ // any PSR-16 adapter (Symfony Cache, Laravel Cache, etc.)
$builder = new CachedJsonBuilder(
container: $container,
cache: $cache,
ttl: 3600, // optional, seconds or \DateInterval
keyPrefix: 'rules.', // optional
);
// First call: parse → serialize → store
// Subsequent calls: deserialize from cache, no parsing
$node = $builder->parseFile('rules/discount.json');
$container->registerShared('my.shared.service', fn (): object => new MySharedService());
$container->registerTransient('my.transient.service', fn (): object => new MyTransientService());
use TheChoice\Context\ContextInterface;
class MyContext implements ContextInterface
{
public function getValue(): mixed
{
return 42; // your business logic here
}
}
$container = new Container(['myContext' => MyContext::class]);
use TheChoice\Context\ContextInterface;
use TheChoice\Operator\AbstractOperator;
class BetweenExclusive extends AbstractOperator
{
public static function getOperatorName(): string
{
return 'betweenExclusive';
}
public function assert(ContextInterface $context): bool
{
$value = $context->getValue();
[$min, $max] = $this->getValue();
return $value > $min && $value < $max;
}
}
// Register in the resolver
$resolver = $container->get(\TheChoice\Operator\OperatorResolverInterface::class);
$resolver->register('betweenExclusive', BetweenExclusive::class);
RuleBuilder::context('depositCount')
->equal(2) // strict equality
->notEqual(0)
->greaterThan(100)
->greaterThanOrEqual(100)
->lowerThan(1000)
->lowerThanOrEqual(999)
->numericInRange([1, 100]) // inclusive range
->arrayContain('vip')
->arrayNotContain('banned')
->containsKey('discount')
->countEqual(3)
->countGreaterThan(0)
->stringContain('prefix')
->stringNotContain('spam')
->startsWith('VIP-')
->endsWith('.ru')
->matchesRegex('/^\d{4}$/')
->isEmpty() // no value needed
->isNull() // no value needed
->isInstanceOf(MyClass::class);
RuleBuilder::context('amount')
->modifier('$context * 0.1') // append one modifier
->modifiers(['$context * 2', '...']) // replace all modifiers
->params(['discountType' => 'vip']) // context parameters
->priority(10) // sort priority in collections
->description('10% of deposit')
->stoppable() // store result on Root and stop
->build();