kevintherm/exprc

Convert expression string to in-memory evaluation, SQL syntax, or something else.

Maintainers

Package info

github.com/kevintherm/exprc

pkg:composer/kevintherm/exprc

Statistics

Installs: 4

Dependents: 1

Suggesters: 0

Stars: 0

Open Issues: 0

v0.0.2 2026-04-04 09:14 UTC

This package is auto-updated.

Last update: 2026-04-04 09:28:42 UTC


README

Exprc is a tiny, decoupled rule engine for PHP

It parses rule strings into an AST once, then lets you evaluate that AST with different backends:

  • in-memory records
  • Laravel query builders
  • custom callback handlers

Prerequisites

  • PHP 8.2+

Install

composer require kevintherm/exprc

For Laravel QueryBuilder support:

composer require illuminate/database

Architecture

Rule string -> Lexer -> Parser -> AST -> Evaluator

Core parser and AST are framework-agnostic. Evaluators are swappable adapters.

Supported Operators

  • = / !=
  • > / >= / < / <=
  • LIKE / NOT LIKE
  • IN / NOT IN
  • CONTAINS / NOT CONTAINS
  • IS NULL / IS NOT NULL

Logical Operators & Precedence

Exprc uses SQL-style keywords for logical operations. Symbols like && or || are not supported.

  • NOT: Inverts the following expression (Highest precedence)
  • AND: Matches only if both sides are true
  • OR: Matches if either side is true (Lowest precedence)

Use parentheses ( ) to group expressions and override default precedence: status = 'active' AND (priority = 'high' OR is_urgent = true)

Comparison Values

  • Literals: 'strings', "strings", 42, 10.5, true, false, null
  • Identifiers: Unquoted words on the right side are treated as field paths (e.g., verified = original_verified)
  • Arrays: ['a', 'b'] or (1, 2, 3)
<?php

use Kevintherm\Exprc\Exprc;

$exprc = new Exprc();

$rule = "user.status = 'active' AND score >= 42";
$record = [
    'user' => ['status' => 'active'],
    'score' => 100,
];

$matches = $exprc->matches($rule, $record);
// true

Parse Once, Evaluate Many

<?php

use Kevintherm\Exprc\Exprc;
use Kevintherm\Exprc\Evaluators\InMemoryEpvaluator;
use Kevintherm\Exprc\Evaluators\CallbackEvaluator;

$exprc = new Exprc();
$ast = $exprc->parse("status IN ['active','pending'] AND tags CONTAINS 'beta'");

$inMemory = InMemoryEvaluator::for([
    'status' => 'active',
    'tags' => ['beta', 'pro'],
])->evaluate($ast);

$callback = CallbackEvaluator::for(function ($comparison): bool {
    return $comparison->field === 'status' && $comparison->value === 'active';
})->evaluate($ast);

Query Builder Usage (Laravel)

Exprc uses Laravel query methods for JSON paths and containment. No raw SQL is generated.

<?php

use Kevintherm\Exprc\Exprc;
use Kevintherm\Exprc\Resolvers\CollectionFieldResolver;

$exprc = new Exprc();
$ast = $exprc->parse("metadata['version'] = 'v2' AND tags CONTAINS 'beta'");

$resolver = new CollectionFieldResolver(
    tableAlias: 'r',
    fieldMap: [
        'status' => 'state',
    ],
);

$query = DB::table('rules as r');
QueryBuilderEvaluator::for($query, $resolver)->evaluate($ast);

$results = $query->get();

Field Access

Supported syntax for parser and in-memory backend:

  • dot notation: user.profile.name
  • bracket notation: tags[0], metadata['key']
  • wildcard notation: items[*]

Array Literals

Use either brackets or parentheses:

  • ['active', 'pending']
  • ('active', 'pending')
  • [1, 'two', true, null]
  • [] or ()

Extension & Customization

Exprc is designed to be highly extensible via composition and visitors.

AST Node Visitors

You can traverse the AST before evaluation for static analysis or transformation using the VisitorInterface.

use Kevintherm\Exprc\Ast\VisitorInterface;
use Kevintherm\Exprc\Ast\ComparisonNode;

class RelationshipFinder implements VisitorInterface {
    public array $relationships = [];
    
    public function visitComparisonNode(ComparisonNode $node): mixed {
        if (str_contains($node->field, '.')) {
            $this->relationships[] = explode('.', $node->field)[0];
        }
        return null;
    }
    // ... implement other methods
}

$finder = new RelationshipFinder();
$ast->accept($finder);
// $finder->relationships now contains all base relations found in the rule

Custom AST Nodes & Evaluator Hooks

All core classes are non-final. You can extend ComparisonNode to create specialized nodes (like VeloquentComparisonNode) or use evaluator hooks:

class MyEvaluator extends InMemoryEvaluator {
    public function beforeProcessNode(Node $node): void {
        // Intercept node before evaluation
    }

    public function afterProcessNode(Node $node, mixed $result): void {
        // Log or transform result
    }
}

Extensible Parsing

The Lexer and Parser classes can be extended. Use the TokenRegistry to prepare for custom symbols (like @ for system variables or -> for JSON paths).

Notes

  • AST nodes are readonly value objects (PHP 8.2+).
  • Metadata-aware nodes can be created by extending base node classes.
  • Evaluators support standard hooks for plugin-like behavior.
  • IS NULL and IS NOT NULL are treated as first-class NullComparisonNode objects.