A lightweight Decision List rule engine for PHP and Laravel.
RuleFlow helps backend teams move complex business rules out of hard-coded if/else logic and into testable, configurable, and traceable rule definitions.
It is designed for risk control, content moderation, marketing eligibility, access control, and business decision workflows.
Many PHP systems start with a few inline checks:
if ($order->amount > 1000 && $user->risk_score < 60) {
return 'reject';
}That works until the rules become:
- scattered across controllers, services, jobs, and listeners
- hard to validate before deployment
- hard to explain to support, operations, or reviewers
- risky to change because business logic is mixed with application flow
RuleFlow keeps the model intentionally small:
- it uses a Decision List model: deterministic priority order, first matching decision by default
- rules are structured data, not framework-specific code
- evaluation is deterministic and priority-based
- decisions can be inspected with
trace()or summarized withexplain() - Laravel integration exists, but the core stays framework-agnostic
RuleFlow is not a RETE inference engine. It is for PHP business decisions where the problem is scattered rules, unsafe changes, and unclear explanations rather than working-memory inference or complex event processing.
- First-match and all-match evaluation with
evaluate()andevaluateAll() - Nested condition groups such as
A AND (B OR C) - Built-in operators for equality, numeric checks, arrays, existence, strings, and regex
- Rule validation before runtime
- Trace diagnostics with failure reasons and timing
- Compact
explain()output for APIs, logs, and support tools - Sensitive-value redaction with
sensitive: true - Optional rule metadata for ownership, versions, tickets, and rollout notes
- Laravel config loading, cache support, and
php artisan ruleflow:validate
RuleFlow is a good fit when you need:
- order or payment risk decisions
- content moderation routing
- campaign or coupon eligibility checks
- access-control decisions with request context
- business rules that need to be reviewed, tested, and explained
It is not trying to be:
- a visual rule builder
- a BPMN or workflow platform
- a distributed decision service
- a replacement for full policy, validation, or workflow systems
Many teams do not need a full decision platform. They need a small library that can live inside an existing PHP service and solve one practical problem: replace scattered conditional business logic with something structured, testable, and explainable.
RuleFlow is intentionally optimized for that use case:
- no external server to run
- no DSL or visual designer to learn
- no database schema required
- no framework lock-in for core usage
- no large integration surface before the first useful rule ships
Compared with heavier rule engines, RuleFlow trades breadth for clarity:
- fewer concepts to learn
- faster adoption inside an existing Laravel or PHP codebase
- easier code review because rules stay close to application context
- easier production debugging because
trace()andexplain()are built in
If your problem is "we need a rules platform", RuleFlow is probably too small. If your problem is "our PHP business logic is turning into untestable if/else sprawl", RuleFlow is the right size.
Current release line: v0.3.x
The project already includes:
- Packagist distribution
- CI with PHPUnit, PHPCS, PHPStan, examples, and install validation
- changelog and GitHub Release flow
- production, security, and Laravel documentation
composer require yl0711-coder/ruleflow-phpcomposer config repositories.ruleflow vcs https://github.com/yl0711-coder/ruleflow-php
composer require yl0711-coder/ruleflow-php:^0.3The package requires PHP 8.1 or later.
use RuleFlow\Engine;
use RuleFlow\RuleSet;
$rules = [
[
'name' => 'high_risk_order',
'priority' => 100,
'match' => 'all',
'conditions' => [
['field' => 'order.amount', 'operator' => '>', 'value' => 1000],
['field' => 'user.risk_score', 'operator' => '<', 'value' => 60],
],
'action' => 'reject',
'reason' => 'High-risk order requires manual review.',
],
];
$context = [
'user' => [
'id' => 1001,
'risk_score' => 45,
],
'order' => [
'id' => 'O-20260420-001',
'amount' => 1299,
],
];
$result = Engine::make(RuleSet::fromArray($rules))->evaluate($context);
$result->matched(); // true
$result->action(); // reject
$result->reason(); // High-risk order requires manual review.
$result->trace(); // explainable rule execution traceRules can also be stored as JSON and loaded at runtime:
use RuleFlow\Engine;
use RuleFlow\Loaders\JsonRuleLoader;
use RuleFlow\Validation\RuleValidator;
$rulesPath = __DIR__ . '/rules/order-risk.json';
$rules = json_decode((string) file_get_contents($rulesPath), true);
RuleValidator::defaults()->assertValid($rules);
$ruleSet = (new JsonRuleLoader($rulesPath))->load();
$result = Engine::make($ruleSet)->evaluate($context);Start here:
Operational usage:
Laravel:
RuleFlow returns a trace so engineers, support teams, and reviewers can understand why a rule matched.
print_r($result->toArray());Example output:
[
'matched' => true,
'rule' => 'high_risk_order',
'action' => 'reject',
'reason' => 'High-risk order requires manual review.',
'trace' => [
[
'rule' => 'high_risk_order',
'priority' => 0,
'matched' => true,
'match' => 'all',
'action' => 'reject',
'reason' => 'High-risk order requires manual review.',
'duration_ms' => 0.042,
'stop_reason' => 'first_match',
'checks' => [
[
'field' => 'order.amount',
'exists' => true,
'missing' => false,
'sensitive' => false,
'actual' => 1299,
'operator' => '>',
'expected' => 1000,
'passed' => true,
'duration_ms' => 0.011,
],
[
'field' => 'user.risk_score',
'exists' => true,
'missing' => false,
'sensitive' => false,
'actual' => 45,
'operator' => '<',
'expected' => 60,
'passed' => true,
'duration_ms' => 0.009,
],
],
],
],
]When a field does not exist, the trace explicitly marks it with exists: false and
missing: true. Disabled rules are marked with skipped: true and
skipped_reason: "disabled".
When a rule or condition fails, RuleFlow includes failure_reason when it can
infer one. Examples include field_missing, type_mismatch,
invalid_expected, value_mismatch, value_not_allowed,
value_not_contained, and pattern_mismatch.
When a condition is marked with sensitive: true, RuleFlow redacts actual
and expected values in trace and explain output as [redacted].
You can also use trace helpers for operational debugging:
$trace = $result->trace();
$trace->matchedRuleNames(); // ['high_risk_order']
$trace->failedEntries(); // rules evaluated but not matched
$trace->skippedEntries(); // disabled rules
$trace->summary(); // matched, failed, skipped, and total durationUse explain() when you want a compact decision summary for logs, API
responses, or support tooling:
$explain = $result->explain();Example output:
[
'matched' => false,
'rule' => null,
'matched_rules' => [],
'action' => null,
'reason' => null,
'failure_reason' => 'field_missing',
'summary' => [
'evaluated_rules' => 1,
'matched_rules' => [],
'failed_rules' => ['phone_present'],
'skipped_rules' => [],
'duration_ms' => 0.031,
],
'rule_explanations' => [
[
'rule' => 'phone_present',
'matched' => false,
'skipped' => false,
'failure_reason' => 'field_missing',
'failed_checks' => [
[
'field' => 'user.phone',
'operator' => 'exists',
'expected' => null,
'actual' => null,
'failure_reason' => 'field_missing',
],
],
],
],
]trace() keeps the full execution detail. explain() keeps the decision
summary small and stable for operational use.
See docs/explain.md, docs/production.md, docs/security-privacy.md, and examples/explain.php.
RuleFlow supports:
=!=>>=<<=innot_inexistsnot_existscontainsstarts_withends_withbetweenregex
= and != use strict PHP comparison semantics (=== / !==).
See docs/semantics.md for the full evaluation contract.
Each rule supports a match mode:
all: every condition must pass. This is the default.any: at least one condition must pass.
Example:
[
'name' => 'suspicious_content',
'match' => 'any',
'conditions' => [
['field' => 'post.content', 'operator' => 'contains', 'value' => 'free money'],
['field' => 'post.report_count', 'operator' => '>=', 'value' => 3],
],
'action' => 'manual_review',
]RuleFlow also supports nested condition groups for cases like A AND (B OR C):
[
'name' => 'high_risk_order',
'match' => 'all',
'conditions' => [
['field' => 'order.amount', 'operator' => '>', 'value' => 1000],
[
'match' => 'any',
'conditions' => [
['field' => 'user.risk_score', 'operator' => '<', 'value' => 60],
['field' => 'user.country', 'operator' => 'in', 'value' => ['NG', 'RU']],
],
],
],
'action' => 'manual_review',
]Nested groups are evaluated recursively and included in the execution trace.
Use evaluateAll() when you need every matched rule instead of only the first one:
$result = Engine::make(RuleSet::fromArray($rules))->evaluateAll($context);
$result->matched(); // true
$result->ruleNames(); // ['amount_review', 'risk_hold']
$result->actions(); // ['manual_review', 'hold']
$result->reasons(); // ['Amount threshold reached.', 'Risk score threshold reached.']This is useful for risk scoring, moderation signals, and rule-based tagging scenarios.
Register a custom operator when built-in operators are not enough:
use RuleFlow\Engine;
use RuleFlow\Operators\OperatorInterface;
use RuleFlow\Operators\OperatorRegistry;
use RuleFlow\RuleSet;
final class RegexOperator implements OperatorInterface
{
public function name(): string
{
return 'regex';
}
public function evaluate(mixed $actual, mixed $expected): bool
{
return is_string($actual)
&& is_string($expected)
&& preg_match($expected, $actual) === 1;
}
}
$operators = OperatorRegistry::defaults();
$operators->register(new RegexOperator());
$result = Engine::makeWithOperators(RuleSet::fromArray($rules), $operators)->evaluate($context);Validate rule definitions before loading them:
use RuleFlow\Validation\RuleValidator;
$validation = RuleValidator::defaults()->validate($rules);
if (!$validation->valid()) {
print_r($validation->errors());
}See docs/validation.md.
Publish the config:
php artisan vendor:publish --tag=ruleflow-configDefine rules in config/ruleflow.php:
'rules' => [
[
'name' => 'new_user_external_link',
'conditions' => [
['field' => 'user.days_since_signup', 'operator' => '<=', 'value' => 7],
['field' => 'post.links', 'operator' => 'contains', 'value' => 'https://example.com'],
],
'action' => 'manual_review',
],
],Optional cache settings:
'cache' => [
'enabled' => true,
'driver' => 'laravel', // or 'in_memory'
'store' => 'redis', // optional, uses default cache store when null
'key' => 'ruleflow.rules',
'ttl' => 300,
],Evaluate rules:
$result = app(\RuleFlow\RuleFlow::class)->evaluate($context);Validate configured rules:
php artisan ruleflow:validateSee docs/laravel.md, docs/laravel-installation.md, and docs/laravel-example.md.
- Risk control: reject or review suspicious orders, users, or API requests.
- Content moderation: route posts, comments, and profiles to review queues.
- Marketing eligibility: decide whether a user can receive a coupon or campaign.
- Access control: evaluate contextual access decisions.
- Business workflow: choose approval paths from structured business conditions.
RuleFlow is intentionally small. The first versions do not aim to provide:
- a visual rule management UI
- a distributed decision platform
- a full workflow engine
- database migrations or admin dashboards
- replacement for Laravel policies, gates, or validation
composer install
composer test
composer lint
composer analyseRun examples:
php examples/order-risk.php
php examples/content-moderation.php
php examples/json-loader.php
php examples/explain.phpGenerate coverage locally when Xdebug or PCOV is available:
composer test-coverageRun the local benchmark:
php benchmarks/evaluate.phpSee docs/benchmark.md.
For production usage recommendations, see docs/production.md.
- v0.1: core engine, built-in operators, trace output, array/JSON loaders, custom operators, rule validation
- v0.2: nested rule groups, evaluateAll, existence operators, built-in regex, trace improvements, Laravel cache driver, artisan validation command, PHPStan, coverage CI
- v0.3: richer trace diagnostics, failure reasons, compact explain output, benchmark suite, production and security guidance
- v0.4: documentation polish, more production examples, API refinement, and ecosystem hardening
- v1.0: stable rule format and semantic versioning guarantee
RuleFlow PHP is open-sourced software licensed under the MIT license.