uuf6429 / expression-language-arrowfunc
Arrow function support in Symfony Expression Language
Package info
github.com/uuf6429/expression-language-arrowfunc
pkg:composer/uuf6429/expression-language-arrowfunc
Requires
- php: ^7.4 || ^8
- symfony/expression-language: ^5.4 || ^6 || ^7 || ^8
Requires (Dev)
- ergebnis/composer-normalize: ^2.7
- phpstan/phpstan: ^2.1
- phpstan/phpstan-phpunit: ^2.0
- phpstan/phpstan-strict-rules: ^2.0
- phpunit/phpunit: ^9.5
This package is auto-updated.
Last update: 2026-07-05 18:45:23 UTC
README
for Symfony Expression Language (5-8)
This library extends Symfony Expression Language component to support Arrow Function (also known as Lambda Functions) syntax.
🔌 Installation
Install via Composer:
composer require uuf6429/expression-language-arrowfunc
💡 Before You Start
1. How does it work?
Essentially, the library pre-processes expressions to replace callbacks with placeholders (variables), then generates new variables (with the callback as value), which are finally later on used for executing the expression. Conceptually:
flowchart TD
A["items<br/> .filter((item) -> { item.type === 'T1' })<br/> .map((item) -> { item.name })"] --> B["Preprocess"]
B --> C["<b>Expression:</b><br/><br/>items<br/> .filter(__lambda_1)<br/> .map(__lambda_2)"]
B --> D["<b>Variables:</b><br/><br/>$items = ... (original variable)<br/>$__lambda_1 = fn (item) => item.type === 'T1'<br/>$__lambda_2 = fn (item) => item.name"]
C --> E["compile() or evaluate()"]
D --> E
classDef default font-family: monospace;
style A text-align: left;
style C text-align: left;
style D text-align: left;
Loading
2. What does the syntax look like?
By default, the arrow function syntax looks like so:
(a) -> { a * 2 }
▲ ▲ ▲
│ │ └── Function body is a single expression that can make
│ │ use of passed parameters or global variables.
│ └───────── The lambda operator - input parameters are to the
│ left and the output expression to the right.
└──────────── Comma-separated list of parameters passed to the
arrow function.
3. How safe is it?
Symfony Expression Language can be unsafe by design – if the result of expressions is used as a callable without being checked, global callables, functions and static methods could be called arbitrarily from (potentially malicious) expressions. This library acknowledges the risk and tries to mitigate it.
Problem Examples
- Exposing functionality that accepts a callable to Expression Language:
$el = new ExpressionLanguage(); $el->addFunction(new ExpressionFunction( 'array_map', static fn ($callback, $array) => sprintf('\array_map(%s, %s)', $callback, $array), static fn (array $variables, callable $callback, array $array) => array_map($callback, $array), )); // Intended expression $result = $el->evaluate('array_map([" aa "], "trim")'); // => ['aa'] // Malicious expression $result = $el->evaluate('array_map([123], "exit")'); // 💥 application exits with code 123
- A naive implementation of arrow functions:
$el = new ExpressionLanguage(); // Intended expression $filter = $el->evaluate('(value) -> { value > 20 }'); $values = array_filter([18, 23, 40], $filter); // => [23, 49] // Malicious expression $filter = $el->evaluate('"exit"'); $values = array_filter([18, 23, 40], $filter); // 💥 application exits with code 18
Solutions
There are two viable solutions:
- Set the type declaration of methods or functions that will receive callbacks to
Closure(notCallable!) - it works, but it is prone to mistakes and frankly, quite risky. - The engine wraps callbacks within an object that cannot be invoked by default – this is the safest option (and the default). Usages need to be aware of this wrapping, drastically decreasing the risk of mistakes.
🚀 Usage
Usage
Two different APIs are provided to suit your integration needs:
1. Ready-Made Drop-in Replacement
If you just need a standard, drop-in replacement for Symfony's standard ExpressionLanguage class, use
uuf6429\ExpressionLanguage\ExpressionLanguageWithArrowFunctions:
use uuf6429\ExpressionLanguage\ExpressionLanguageWithArrowFunctions; $el = new ExpressionLanguageWithArrowFunctions(); $phpCode = $el->compile('(val) -> { val * 2 }', []); assert($phpCode === 'function ($val) { return ($val * 2); }');
2. Extensible Trait
If you want to integrate this functionality with your own custom ExpressionLanguage implementation or combine it with
other classes, use the trait uuf6429\ExpressionLanguage\ArrowFunctionTrait:
use Symfony\Component\ExpressionLanguage\ExpressionLanguage as SymfonyExpressionLanguage; use Symfony\Component\ExpressionLanguage\ParsedExpression as SymfonyParsedExpression; use uuf6429\ExpressionLanguage\ArrowFunctionTrait; class MyCustomExpressionLanguage { use ArrowFunctionTrait; public function evaluate($expression, $values = []) { return $this->evaluateWithArrowFunctions($expression, $values); } public function compile($expression, $names = []) { return $this->compileWithArrowFunctions($expression, $names); } protected function compileWithoutArrowFunctions($expression, array $names = []): string { // TODO Implement method } protected function evaluateWithoutArrowFunctions($expression, array $values = []) { // TODO Implement method } protected function parseWithoutArrowFunctions($expression, array $names = []): SymfonyParsedExpression { // TODO Implement method } protected function lintWithoutArrowFunctions($expression, array $names = []): void { // TODO Implement method } }
Important
For safety reasons, callbacks are wrapped in a SafeCallable object. Your methods and functions need to expect
and handle that object. This is to avoid expressions being able to "break out" and execute anything.
Here's a more complete example:
use Symfony\Component\ExpressionLanguage\ExpressionFunction; use uuf6429\ExpressionLanguage\ExpressionLanguageWithArrowFunctions; use uuf6429\ExpressionLanguage\SafeCallable; $el = new ExpressionLanguageWithArrowFunctions(); // Expose array_map() as map() $el->addFunction(new ExpressionFunction( 'map', static fn (string $callbackExpr, string $arrayExpr) => sprintf('\array_map(%s->getCallback(), %s)', $callbackExpr, $arrayExpr), static fn (array $variables, SafeCallable $callback, array $array) => array_map($callback->getCallback(), $array), )); // Compiling $phpCode = $el->compile('map((val) -> { val * 2 }, values)', ['values']); assert($phpCode === '\array_map(function ($val) { return ($val * 2); }->getCallback(), $values)'); // Evaluating $result = $el->evaluate('map((val) -> { val * 2 }, values)', ['values' => [1, 2, 3]]); assert($result === [2, 4, 6]);