Skip to main content

Introduction

Expression Engine evaluates a short string against a bag of structured data and gives you back a real, typed value:

$engine->evaluate('trigger.user.age >= 18', [
'trigger' => ['user' => ['age' => 21]],
]);
// → true (a real bool)

It also fills {{ }} placeholders in longer strings:

$engine->resolve('Hi {{ trigger.user.name }}, you have {{ trigger.cart.items.count() }} items', [
'trigger' => [
'user' => ['name' => 'Ada'],
'cart' => ['items' => [/* ... */]],
],
]);
// → "Hi Ada, you have 3 items"

When to reach for it

The engine is built for low-code surfaces where a non-developer, or a piece of stored configuration, needs to express a dynamic value:

  • Workflow automations — "run this step only if trigger.order.total > 100".
  • Dynamic forms — computed fields, conditional visibility rules.
  • Templated config and notifications — subject lines, webhook payloads, message bodies with data interpolated in.

It is deliberately not a general-purpose scripting language. There are no user-defined variables (beyond set()/get() within a run), no loops, no assignment, and no access to arbitrary PHP. Expressions read data, transform it through a fixed, safe set of methods and functions, and produce a value.

Installation

composer require qanna-rsa/expression-engine

Requires PHP 8.1+ and Laravel 10, 11, 12, or 13.

Laravel auto-discovers the service provider and the ExpressionEngine facade. Publish the config file if you want to change defaults:

php artisan vendor:publish --tag=expression-engine-config

This writes config/expression-engine.php.

Quickstart

Resolve the engine from the container (it is registered as a singleton, configured from config/expression-engine.php):

use Qanna\ExpressionEngine\Engine;

$engine = app(Engine::class);

Or use the facade:

use Qanna\ExpressionEngine\Facades\ExpressionEngine;

ExpressionEngine::evaluate('trigger.total * 1.15', ['trigger' => ['total' => 200]]);
// → 230.0

Evaluate an expression

$engine->evaluate('trigger.items.filter(current.active).map(current.name)', [
'trigger' => ['items' => [
['name' => 'Widget', 'active' => true],
['name' => 'Gadget', 'active' => false],
]],
]);
// → ["Widget"]

Fill a template

$engine->resolve('Order {{ trigger.order.ref }} — {{ trigger.order.total.format(2) }}', [
'trigger' => ['order' => ['ref' => 'A-1001', 'total' => 49.5]],
]);
// → "Order A-1001 — 49.50"

Resolve every string in a structure

resolveArray() walks an array recursively and resolves {{ }} in every string value — handy for templated notification or webhook payloads:

$engine->resolveArray([
'to' => '{{ trigger.user.email }}',
'subject' => 'Welcome, {{ trigger.user.name }}',
'body' => [
'greeting' => 'Hi {{ trigger.user.name.title() }}',
],
], $context);

Where to go next