qanna-rsa / expression-engine
A small, fast expression language for evaluating dynamic values against structured data -- dot-path access, list filtering/mapping, chainable string/number/date methods, and {{ }} template interpolation for Laravel workflows, forms, and low-code automations.
Requires
- php: ^8.1
- illuminate/console: ^10.0|^11.0|^12.0|^13.0
- illuminate/support: ^10.0|^11.0|^12.0|^13.0
- nesbot/carbon: ^2.0|^3.0
Requires (Dev)
- orchestra/testbench: ^11.1
- phpunit/phpunit: ^13.1
Suggests
- fakerphp/faker: Required for the /fake command in the interactive playground (php artisan expression:playground).
This package is not auto-updated.
Last update: 2026-08-08 21:38:51 UTC
README
A small, fast expression language for evaluating dynamic values against structured data — dot-path lookups, list filtering/mapping, string/number/date manipulation, and template interpolation, all from a single string.
Overview
Built for low-code surfaces — workflow automations, dynamic forms, templated configs — anywhere you'd reach for a spreadsheet formula or a Zapier/Make-style expression, but need it embedded in your own PHP/Laravel app. Wrap an expression in {{ }} inside any string, or evaluate one directly and get a real typed value back.
trigger.items.filter(current.active).map(current.name.upper()).first()
Features
- Dot-path access —
trigger.user.email, with safe navigation (?.) and null-coalescing (??) for data that might not exist - Chainable methods on strings, numbers, arrays, and dates —
trigger.name.upper().trim(),now().addDays(5).format('Y-m-d') - List operations —
filter(),map(),sort(),any(),every(),groupBy(), usingcurrentfor the item in scope; chains and nests cleanly - Full operator set — arithmetic, comparison, logical (
&&||!), ternary (? :), null-coalescing (??) - Template interpolation —
resolve()replaces every{{ expr }}in a string; a single bare expression keeps its real type (int, array, bool…), mixed text stringifies - Code blocks — multi-line sequences via
run(), sharing state between lines withset()/get() - Extensible — register your own functions and chainable methods without touching the engine
- Introspection API —
availableMethods(),availableFunctions(),suggestMethods(),suggestKeys(), for building autocomplete on top of the language - Interactive playground —
php artisan expression:playground, a tinker-style REPL with live context editing, Eloquent factory / Faker data generation, and inline parse-error diagnostics - Strict or forgiving — configurable: unresolved paths either throw or quietly resolve to
null
Installation
composer require qanna-rsa/expression-engine
Laravel auto-discovers the service provider and facade. Optionally publish the config:
php artisan vendor:publish --tag=expression-engine-config
Quick Start
use Qanna\ExpressionEngine\Engine; $engine = new Engine(config('expression-engine')); // Fill a template $engine->resolve('Hello {{ trigger.name }}', [ 'trigger' => ['name' => 'John'], ]); // → "Hello John" // Evaluate an expression and get a real typed value back $engine->evaluate('trigger.items.filter(current.active).map(current.name)', [ 'trigger' => ['items' => [ ['name' => 'Widget', 'active' => true], ['name' => 'Gadget', 'active' => false], ]], ]); // → ["Widget"]
Playground
php artisan expression:playground drops you into a REPL against a live Engine + Context — no PHP file needed to try an expression:
$ php artisan expression:playground
expr › /fake
Fake context generated under "trigger". Try: trigger.items.filter(current.active).map(current.name)
expr › trigger.items.filter(current.active).map(current.name)
=> ["voluptatibus tenetur","placeat odio","eum et"] (array, 3.12ms)
chainable: all, any, avg, chunk, contains, count, diff …+30 more
expr › trigger.name.upper(
ParserException: Unexpected token '' at position 19.
trigger.name.upper(
^
/fakeseeds realistic demo data instantly;/factory App\Models\Orderpulls from your app's real Eloquent factories instead (->make()only — it never touches the database)/set trigger.age 21and/contextbuild up and inspect context by hand/blocktests multi-lineset()/get()code blocks likerun()/suggest <expr>,/methods,/functionsintrospect the language live — the same API you'd use to build autocomplete- Every result shows its type and timing, plus what you can chain next; parse errors point at the exact character that broke