Expressions and templates
There are two ways to run an expression, and the difference is entirely about what comes back.
evaluate() — one expression, one typed value
evaluate() takes a raw expression string with no delimiters and returns
whatever it resolves to, with its type intact:
$engine->evaluate('trigger.order.total', ['trigger' => ['order' => ['total' => 49.5]]]);
// → 49.5 (float)
$engine->evaluate('trigger.order.paid', ['trigger' => ['order' => ['paid' => true]]]);
// → true (bool)
$engine->evaluate('trigger.order.lines', $context);
// → [ ... ] (array)
Use evaluate() whenever you need the value itself — a condition to
branch on, a number to store, a list to iterate.
resolve() — a string with {{ }} placeholders
resolve() scans a string for expressions wrapped in the configured
delimiters ({{ }} by default) and replaces each one:
$engine->resolve('Total due: {{ trigger.order.total }}', $context);
// → "Total due: 49.5"
The single-expression exception
If the string is nothing but one placeholder — no surrounding text —
resolve() returns the raw typed value instead of a string:
$engine->resolve('{{ trigger.order.lines }}', $context);
// → [ ... ] (the array, not its JSON string)
$engine->resolve('{{ trigger.order.paid }}', $context);
// → true (bool)
This lets you store one template string in a config field and use it both for display ("Total: {{ ... }}") and for producing a real value ("{{ ... }}") without a separate code path.
As soon as there is any other text or a second placeholder, every result is converted to a string and spliced in:
nullbecomes an empty stringtrue/falsebecome"true"/"false"- arrays become compact JSON
- everything else is cast with
(string)
$engine->resolve('Paid: {{ trigger.order.paid }}', $context);
// → "Paid: true"
$engine->resolve('Value: {{ trigger.order.missing }}', $context);
// → "Value: "
resolveArray() — resolve every string in a structure
resolveArray() walks an array recursively and runs resolve() on every
string value it finds. Non-string values (ints, bools, null) pass
through untouched. Nested arrays are walked in place.
$engine->resolveArray([
'to' => '{{ trigger.user.email }}',
'subject' => 'Welcome, {{ trigger.user.name }}',
'meta' => [
'item_count' => '{{ trigger.cart.items.count() }}',
'flagged' => true,
],
], $context);
Because the array's string values each go through resolve(), the
single-expression exception applies per value: 'item_count' above
comes back as an integer, while 'subject' comes back as an
interpolated string.
Passing context
All three methods take the data as their second argument. It can be a
plain associative array (the common case) or a
ContextContract
instance if you need custom resolution behaviour.
$engine->evaluate('trigger.user.name', ['trigger' => ['user' => ['name' => 'Ada']]]);
Caching within a call
Within a single resolve(), resolveArray(), or run() call, identical
expressions evaluated against identical context are computed once and
reused. Parsed expressions (the AST) are also cached on the engine
instance and reused across calls. Both behaviours are on by default and
can be turned off — see Configuration.