PHPackages                             ols/php-ruler - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Validation &amp; Sanitization](/categories/validation)
4. /
5. ols/php-ruler

ActiveLibrary[Validation &amp; Sanitization](/categories/validation)

ols/php-ruler
=============

A transparent expression &amp; rule evaluator in pure PHP. No dependencies. Strict typing, a safe mode that collects missing variables instead of failing, and an explain mode that shows exactly why a rule passed or failed. Move business logic out of your code.

v1.0.0(1mo ago)31↓87.5%MITPHPPHP &gt;=8.1CI passing

Since Jun 6Pushed 1mo agoCompare

[ Source](https://github.com/olivier-ls/php-ruler)[ Packagist](https://packagist.org/packages/ols/php-ruler)[ Docs](https://github.com/olivier-ls/php-ruler)[ RSS](/packages/ols-php-ruler/feed)WikiDiscussions main Synced 1w ago

READMEChangelogDependencies (1)Versions (2)Used By (0)

php-ruler
=========

[](#php-ruler)

[![CI](https://github.com/olivier-ls/php-ruler/actions/workflows/ci.yml/badge.svg)](https://github.com/olivier-ls/php-ruler/actions/workflows/ci.yml)[![Packagist Version](https://camo.githubusercontent.com/610c05587c114890124601dc1be3a8f377c358eba0b7e045594b5f275ecf4f87/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6f6c732f7068702d72756c6572)](https://packagist.org/packages/ols/php-ruler)[![PHP Version](https://camo.githubusercontent.com/e9778dd38917d9e8ed3019e9b11e59cb16837df60750ccf23a56260c975f5f38/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f646570656e64656e63792d762f6f6c732f7068702d72756c65722f706870)](https://packagist.org/packages/ols/php-ruler)[![License](https://camo.githubusercontent.com/0313ce5cd399a3821fe24e729aab444cdff4ce4f176b478208a9ade74fc2b970/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6963656e73652f6f6c69766965722d6c732f7068702d72756c6572)](LICENSE)[![Downloads](https://camo.githubusercontent.com/367ab3267e06be0371c47eb0cf42013da14738ae13d558cb33958f412bc7a152/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f6f6c732f7068702d72756c6572)](https://packagist.org/packages/ols/php-ruler)

Evaluate business rules stored as strings — with strict typing and a node-by-node trace of why each rule passed or failed.

`Strict typing` · `Safe mode` · `Full evaluation trace` · `Variable aliasing` · `Zero dependencies` · `PHP 8.1+`

---

Who is this for?
----------------

[](#who-is-this-for)

php-ruler exists to move business logic *out* of your code and into expressions that can be stored, edited, and evaluated at runtime — pricing rules, eligibility checks, feature flags, validation conditions, content filters — without redeploying. It is also a practical way to let non-developers author rules, because every evaluation can be explained step by step.

If you need to traverse objects, call methods, or you are already invested in the Symfony stack, Symfony's [ExpressionLanguage](https://symfony.com/doc/current/components/expression_language.html)is the mature, battle-tested choice. php-ruler deliberately does **less**: it never touches objects, methods, or PHP internals. That is precisely what lets it stay strict, dependency-free, and safe to expose to semi-trusted rule authors — and what lets it tell you *why* a rule evaluated the way it did.

**It is a good fit if:**

- You want to store and evaluate rules without redeploying code
- You want strict, predictable semantics (no silent type juggling)
- You let users or non-developers write rules, and need to explain the outcome
- You want zero dependencies and nothing to install beyond the library itself

**It is not a good fit if:**

- You need to read object properties or call methods inside expressions
- You need the full operator surface of Twig/Symfony ExpressionLanguage
- You want to run arbitrary, fully untrusted input without any review (see [Security](#security))

---

Features
--------

[](#features)

- **Strict evaluation** — no type coercion. `1 = '1'` is a type error, not `true`. NaN/INF can never silently slip through an operator. ([`docs/evaluate.md`](docs/evaluate.md))
- **Safe mode** — evaluate against a partial context: instead of throwing on the first missing variable, it collects every missing path so you can report them all at once. ([`docs/evaluate-safe.md`](docs/evaluate-safe.md))
- **Explain mode** — turn any expression into a tree showing, leaf by leaf, what was evaluated, what passed or failed, what was short-circuited, and *why* something could not be evaluated (the missing variable, the type error). ([`docs/explainer.md`](docs/explainer.md))
- **Variable aliasing** — translate between a human-facing form (`"cart amount > 100"`) and the technical paths your context actually uses (`cart.total > 100`), in both directions. ([`docs/alias-resolver.md`](docs/alias-resolver.md))
- **AST caching &amp; export** — parsed expressions are cached (LRU); ASTs can be exported to JSON and re-imported, so you can pre-compile once and store the result. ([`docs/ast-management.md`](docs/ast-management.md))
- **Custom functions** — register your own callables alongside the built-ins. ([`docs/functions.md`](docs/functions.md))
- **A clear error model** — typed exceptions with structured details (the offending variable path, the syntax error position). ([`docs/exceptions.md`](docs/exceptions.md))
- **No extensions required** — runs on any standard PHP 8.1+ installation.

---

Requirements
------------

[](#requirements)

- PHP **8.1** or higher
- No PHP extensions, no external services, no dependencies

---

Installation
------------

[](#installation)

**Via Composer**

```
composer require ols/php-ruler
```

**Manual install** — if you are not using Composer, copy the `src/` directory into your project and include its autoloader:

```
require '/path/to/php-ruler/src/autoload.php';
```

---

Quick start
-----------

[](#quick-start)

```
use Ols\PhpRuler\ExpressionEvaluator;

$eval = new ExpressionEvaluator();

$context = [
    'cart'     => ['total' => 150.00],
    'customer' => ['group' => 'vip', 'vip' => true],
    'product'  => ['price' => 49.99],
];

$eval->evaluate("cart.total > 100 AND customer.group = 'vip'", $context); // true
```

Variables are read from the context by dot-notation path. Evaluation is strict: comparing incompatible types, or using a missing variable, raises a typed exception rather than guessing.

---

Evaluating
----------

[](#evaluating)

The strict entry points throw on the first problem (missing variable, type error, syntax error):

```
$eval->evaluate("round(cart.total * 0.9, 2)", $context);   // mixed  → 135.0
$eval->evaluateBoolean("cart.total >= 50", $context);      // bool   → true
$eval->evaluateNumeric("product.price * 2", $context);     // float  → 99.98
```

→ Detailed documentation: [`docs/evaluate.md`](docs/evaluate.md)

Safe mode
---------

[](#safe-mode)

When the context may be incomplete, `evaluateSafe()` does not throw on missing variables — it reports them so you can decide what to do:

```
$result = $eval->evaluateSafe("cart.total > customer.creditLimit", $context);

$result->success;       // false
$result->missingVars;   // ['customer.creditLimit']
$result->getValueOr(false);
```

→ Detailed documentation: [`docs/evaluate-safe.md`](docs/evaluate-safe.md)

Explain mode
------------

[](#explain-mode)

`explain()` returns the full evaluation tree — the part that makes a rule auditable instead of a black box:

```
use Ols\PhpRuler\Explainer\ExpressionExplainer;

$explainer = new ExpressionExplainer($eval);
$result    = $explainer->explain(
    "customer.vip = true AND cart.total >= 50 AND product.price < 10 OR customer.group = 'vip'",
    $context
);

$result->passed;        // true | false | null (null = could not be fully evaluated)
$result->failures();    // evaluated leaves that returned false
$result->missing();     // leaves that needed an absent variable
$result->root;          // the full tree (expression, status, passed, resolved values, children)
```

Each node carries its reconstructed sub-expression, its status (evaluated / short-circuited / missing / error), the resolved left/right values, and — for missing or errored nodes — a detail string (the variable path or the error message).

→ Detailed documentation: [`docs/explainer.md`](docs/explainer.md)

Variable aliasing
-----------------

[](#variable-aliasing)

`AliasResolver` is a standalone, two-way text translator between a human-facing form and your technical paths. It does not parse or evaluate — it only substitutes — so it composes cleanly with the evaluator:

```
use Ols\PhpRuler\AliasResolver;

$resolver = (new AliasResolver())
    ->add('customer.group', 'customer group')
    ->add('cart.total',     'cart amount');

// Human input → technical expression → evaluate
$expr = $resolver->humanToExpression("customer group = 'vip' AND cart amount > 100");
// "customer.group = 'vip' AND cart.total > 100"
$eval->evaluate($expr, $context);

// Stored technical expression → human form for display
$resolver->expressionToHuman("cart.total > 100"); // "cart amount > 100"
```

→ Detailed documentation: [`docs/alias-resolver.md`](docs/alias-resolver.md)

---

What you can express
--------------------

[](#what-you-can-express)

From a simple condition to a multi-criteria rule combining functions, dates, and fallback logic:

```
// Basic comparison
"cart.total > 100"
"user.role = 'admin'"

// Membership
"product.category IN ['clothing', 'shoes', 'boots']"
"article.status NOT IN ['draft', 'archived']"

// Combining conditions
"cart.total > 100 AND customer.group = 'vip'"
"article.author = user.id OR user.role = 'editor'"

// Null-coalescing and ternary
"customer.discount ?? 0"
"(customer.score ?? 0) >= 50"
"user.plan = 'pro' ? 'full_access' : 'limited'"

// Functions — math, strings, dates
"round(cart.total * 1.2, 2) < 500"
"dateDiff(today(), article.published_at) = 100 AND cart.country IN ['FR', 'BE', 'CH']"
"article.status = 'published' AND dateDiff(today(), article.published_at) = 50 AND NOT customer.country IN ['IR', 'KP'] AND dateAdd(user.created_at, 30, 'day') < today()"

// Not just boolean rules — expressions can return values
"customer.vip ? round(cart.total * 0.85, 2) : cart.total"
// → float: the discounted price, or the original

"clamp((customer.score ?? 0) / 10, 0, 100)"
// → a numeric score between 0 and 100

"dateDiff(today(), subscription.expires_at) = 100 AND shipping country IN ['FR', 'BE', 'CH']"

// You can also register custom functions and call them directly in expressions:
// "is_eligible(customer.score, plan.threshold) AND quota.remaining > 0"
```

All types, operators, functions, and precedence rules are in [`docs/language-reference.md`](docs/language-reference.md) and [`docs/functions.md`](docs/functions.md).

---

Security
--------

[](#security)

php-ruler evaluates only the context you pass in. It cannot read object properties, call methods, reach PHP constants, or touch the filesystem — there is no escape hatch into the host. That makes it well suited to evaluating rules authored in a back office or by semi-trusted users.

It is not, however, a hardened sandbox for arbitrary hostile input out of the box: a registered regex-style custom function could expose ReDoS, and pathologically deep expressions are bounded by a depth guard but still cost CPU. Review or constrain rules that come from fully untrusted sources.

---

Documentation
-------------

[](#documentation)

TopicDocumentLanguage reference (grammar, operators, precedence, typing)[`docs/language-reference.md`](docs/language-reference.md)Built-in functions[`docs/functions.md`](docs/functions.md)Evaluating (strict)[`docs/evaluate.md`](docs/evaluate.md)Safe mode[`docs/evaluate-safe.md`](docs/evaluate-safe.md)Explain mode[`docs/explainer.md`](docs/explainer.md)Variable aliasing[`docs/alias-resolver.md`](docs/alias-resolver.md)Context resolution[`docs/context.md`](docs/context.md)AST caching &amp; export[`docs/ast-management.md`](docs/ast-management.md)Static analysis (extract variables / functions)[`docs/static-analysis.md`](docs/static-analysis.md)Exceptions &amp; error model[`docs/exceptions.md`](docs/exceptions.md)---

Demo
----

[](#demo)

A small local playground to write expressions and **see the evaluation trace** — which sub-conditions passed, which failed, which were short-circuited, and, when a variable is missing or a value has the wrong type, *why* the rule could not be evaluated.

No build step, no Composer required:

```
php -S localhost:8000 -t demo
```

Then open .

[![php-ruler demo — an expression evaluated, with its full evaluation trace](https://github.com/olivier-ls/php-ruler/raw/main/demo/demo.png)](https://github.com/olivier-ls/php-ruler/raw/main/demo/demo.png)

See [`demo/README.md`](demo/README.md) for details.

---

License
-------

[](#license)

MIT — see [LICENSE](LICENSE).

###  Health Score

38

—

LowBetter than 83% of packages

Maintenance90

Actively maintained with recent releases

Popularity5

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity42

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

48d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/247223033?v=4)[olivier-ls](/maintainers/olivier-ls)[@olivier-ls](https://github.com/olivier-ls)

---

Top Contributors

[![olivier-ls](https://avatars.githubusercontent.com/u/247223033?v=4)](https://github.com/olivier-ls "olivier-ls (5 commits)")

---

Tags

expressionrulesstrictexplainno-dependenciesevaluatorbusiness-rulesrule-enginepredicateexpression-language

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/ols-php-ruler/health.svg)

```
[![Health](https://phpackages.com/badges/ols-php-ruler/health.svg)](https://phpackages.com/packages/ols-php-ruler)
```

###  Alternatives

[ruler/ruler

A simple stateless production rules engine for modern PHP.

1.1k835.4k4](/packages/ruler-ruler)[mi-schi/phpmd-extension

Contains extra phpmd rules from clean code book and the best practices of my experiences.

40908.0k5](/packages/mi-schi-phpmd-extension)[ballen/plexity

A password complexity checker library.

10882.0k](/packages/ballen-plexity)[suin/php-cs-fixer-rules

A Rule set for PHP-CS-Fixer mainly targeting PHP 7.1 or higher.

13123.3k3](/packages/suin-php-cs-fixer-rules)[hybridlogic/validation

A simple, extensible validation library for PHP with support for filtering and validating any input array along with generating client side validation code.

641.1k](/packages/hybridlogic-validation)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
