uuf6429/expression-language-arrowfunc

Arrow function support in Symfony Expression Language

Maintainers

Package info

github.com/uuf6429/expression-language-arrowfunc

pkg:composer/uuf6429/expression-language-arrowfunc

Transparency log

Statistics

Installs: 13

Dependents: 0

Suggesters: 0

Stars: 4

Open Issues: 2

1.0.1 2026-07-05 18:23 UTC

README

for Symfony Expression Language (5-8)

CI codecov Minimum PHP Version License Latest Stable Version Latest Unstable Version

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/>&nbsp;&nbsp;.filter((item) -> { item.type === 'T1' })<br/>&nbsp;&nbsp;.map((item) -> { item.name })"] --> B["Preprocess"]
	B --> C["<b>Expression:</b><br/><br/>items<br/>&nbsp;&nbsp;.filter(__lambda_1)<br/>&nbsp;&nbsp;.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

  1. 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
  2. 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:

  1. Set the type declaration of methods or functions that will receive callbacks to Closure (not Callable!) - it works, but it is prone to mistakes and frankly, quite risky.
  2. 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]);