Skip to content

The ExpressionLanguage Component

Warning: You are browsing the documentation for Symfony 6.1, which is no longer maintained.

Read the updated version of this page for Symfony 7.1 (the current stable version).

The ExpressionLanguage Component

The ExpressionLanguage component provides an engine that can compile and evaluate expressions. An expression is a one-liner that returns a value (mostly, but not limited to, Booleans).

Installation

1
$ composer require symfony/expression-language

Note

If you install this component outside of a Symfony application, you must require the vendor/autoload.php file in your code to enable the class autoloading mechanism provided by Composer. Read this article for more details.

How can the Expression Engine Help Me?

The purpose of the component is to allow users to use expressions inside configuration for more complex logic. For some examples, the Symfony Framework uses expressions in security, for validation rules and in route matching.

Besides using the component in the framework itself, the ExpressionLanguage component is a perfect candidate for the foundation of a business rule engine. The idea is to let the webmaster of a website configure things in a dynamic way without using PHP and without introducing security problems:

1
2
3
4
5
6
7
8
# Get the special price if
user.getGroup() in ['good_customers', 'collaborator']

# Promote article to the homepage when
article.commentCount > 100 and article.category not in ["misc"]

# Send an alert when
product.stock < 15

Expressions can be seen as a very restricted PHP sandbox and are immune to external injections as you must explicitly declare which variables are available in an expression.

Usage

The ExpressionLanguage component can compile and evaluate expressions. Expressions are one-liners that often return a Boolean, which can be used by the code executing the expression in an if statement. A simple example of an expression is 1 + 2. You can also use more complicated expressions, such as someArray[3].someMethod('bar').

The component provides 2 ways to work with expressions:

  • evaluation: the expression is evaluated without being compiled to PHP;
  • compile: the expression is compiled to PHP, so it can be cached and evaluated.

The main class of the component is ExpressionLanguage:

1
2
3
4
5
6
7
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;

$expressionLanguage = new ExpressionLanguage();

var_dump($expressionLanguage->evaluate('1 + 2')); // displays 3

var_dump($expressionLanguage->compile('1 + 2')); // displays (1 + 2)

Expression Syntax

The ExpressionLanguage component uses a specific syntax which is based on the expression syntax of Twig. In this document, you can find all supported syntaxes.

Supported Literals

The component supports:

  • strings - single and double quotes (e.g. 'hello')
  • numbers - integers (e.g. 103), decimals (e.g. 9.95), decimals without leading zeros (e.g. .99, equivalent to 0.99); all numbers support optional underscores as separators to improve readability (e.g. 1_000_000, 3.14159_26535)
  • arrays - using JSON-like notation (e.g. [1, 2])
  • hashes - using JSON-like notation (e.g. { foo: 'bar' })
  • booleans - true and false
  • null - null
  • exponential - also known as scientific (e.g. 1.99E+3 or 1e-2)

6.1

Support for decimals without leading zeros and underscore separators were introduced in Symfony 6.1.

Caution

A backslash (\) must be escaped by 4 backslashes (\\\\) in a string and 8 backslashes (\\\\\\\\) in a regex:

1
2
echo $expressionLanguage->evaluate('"\\\\"'); // prints \
$expressionLanguage->evaluate('"a\\\\b" matches "/^a\\\\\\\\b$/"'); // returns true

Control characters (e.g. \n) in expressions are replaced with whitespace. To avoid this, escape the sequence with a single backslash (e.g. \\n).

Working with Objects

When passing objects into an expression, you can use different syntaxes to access properties and call methods on the object.

Accessing Public Properties

Public properties on objects can be accessed by using the . syntax, similar to JavaScript:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Apple
{
    public $variety;
}

$apple = new Apple();
$apple->variety = 'Honeycrisp';

var_dump($expressionLanguage->evaluate(
    'fruit.variety',
    [
        'fruit' => $apple,
    ]
));

This will print out Honeycrisp.

Calling Methods

The . syntax can also be used to call methods on an object, similar to JavaScript:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Robot
{
    public function sayHi($times)
    {
        $greetings = [];
        for ($i = 0; $i < $times; $i++) {
            $greetings[] = 'Hi';
        }

        return implode(' ', $greetings).'!';
    }
}

$robot = new Robot();

var_dump($expressionLanguage->evaluate(
    'robot.sayHi(3)',
    [
        'robot' => $robot,
    ]
));

This will print out Hi Hi Hi!.

Null-safe Operator

Use the ?. syntax to access properties and methods of objects that can be null (this is equivalent to the $object?->propertyOrMethod PHP null-safe operator):

1
2
3
4
5
6
7
// these will throw an exception when `fruit` is `null`
$expressionLanguage->evaluate('fruit.color', ['fruit' => '...'])
$expressionLanguage->evaluate('fruit.getStock()', ['fruit' => '...'])

// these will return `null` if `fruit` is `null`
$expressionLanguage->evaluate('fruit?.color', ['fruit' => '...'])
$expressionLanguage->evaluate('fruit?.getStock()', ['fruit' => '...'])

6.1

The null safe operator was introduced in Symfony 6.1.

Working with Functions

You can also use registered functions in the expression by using the same syntax as PHP and JavaScript. The ExpressionLanguage component comes with one function by default: constant(), which will return the value of the PHP constant:

1
2
3
4
5
define('DB_USER', 'root');

var_dump($expressionLanguage->evaluate(
    'constant("DB_USER")'
));

This will print out root.

Tip

To read how to register your own functions to use in an expression, see "The ExpressionLanguage Component".

Working with Arrays

If you pass an array into an expression, use the [] syntax to access array keys, similar to JavaScript:

1
2
3
4
5
6
7
8
$data = ['life' => 10, 'universe' => 10, 'everything' => 22];

var_dump($expressionLanguage->evaluate(
    'data["life"] + data["universe"] + data["everything"]',
    [
        'data' => $data,
    ]
));

This will print out 42.

Supported Operators

The component comes with a lot of operators:

Arithmetic Operators

  • + (addition)
  • - (subtraction)
  • * (multiplication)
  • / (division)
  • % (modulus)
  • ** (pow)

For example:

1
2
3
4
5
6
7
8
var_dump($expressionLanguage->evaluate(
    'life + universe + everything',
    [
        'life' => 10,
        'universe' => 10,
        'everything' => 22,
    ]
));

This will print out 42.

Bitwise Operators

  • & (and)
  • | (or)
  • ^ (xor)

Comparison Operators

  • == (equal)
  • === (identical)
  • != (not equal)
  • !== (not identical)
  • < (less than)
  • > (greater than)
  • <= (less than or equal to)
  • >= (greater than or equal to)
  • matches (regex match)
  • contains
  • starts with
  • ends with

6.1

The contains, starts with and ends with operators were introduced in Symfony 6.1.

Tip

To test if a string does not match a regex, use the logical not operator in combination with the matches operator:

1
$expressionLanguage->evaluate('not ("foo" matches "/bar/")'); // returns true

You must use parentheses because the unary operator not has precedence over the binary operator matches.

Examples:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ret1 = $expressionLanguage->evaluate(
    'life == everything',
    [
        'life' => 10,
        'everything' => 22,
    ]
);

$ret2 = $expressionLanguage->evaluate(
    'life > everything',
    [
        'life' => 10,
        'everything' => 22,
    ]
);

Both variables would be set to false.

Logical Operators

  • not or !
  • and or &&
  • or or ||

For example:

1
2
3
4
5
6
7
8
$ret = $expressionLanguage->evaluate(
    'life < universe or life < everything',
    [
        'life' => 10,
        'universe' => 10,
        'everything' => 22,
    ]
);

This $ret variable will be set to true.

String Operators

  • ~ (concatenation)

For example:

1
2
3
4
5
6
7
var_dump($expressionLanguage->evaluate(
    'firstName~" "~lastName',
    [
        'firstName' => 'Arthur',
        'lastName' => 'Dent',
    ]
));

This would print out Arthur Dent.

Array Operators

  • in (contain)
  • not in (does not contain)

For example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class User
{
    public $group;
}

$user = new User();
$user->group = 'human_resources';

$inGroup = $expressionLanguage->evaluate(
    'user.group in ["human_resources", "marketing"]',
    [
        'user' => $user,
    ]
);

The $inGroup would evaluate to true.

Numeric Operators

  • .. (range)

For example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class User
{
    public $age;
}

$user = new User();
$user->age = 34;

$expressionLanguage->evaluate(
    'user.age in 18..45',
    [
        'user' => $user,
    ]
);

This will evaluate to true, because user.age is in the range from 18 to 45.

Ternary Operators

  • foo ? 'yes' : 'no'
  • foo ?: 'no' (equal to foo ? foo : 'no')
  • foo ? 'yes' (equal to foo ? 'yes' : '')

Passing in Variables

You can also pass variables into the expression, which can be of any valid PHP type (including objects):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;

$expressionLanguage = new ExpressionLanguage();

class Apple
{
    public $variety;
}

$apple = new Apple();
$apple->variety = 'Honeycrisp';

var_dump($expressionLanguage->evaluate(
    'fruit.variety',
    [
        'fruit' => $apple,
    ]
)); // displays "Honeycrisp"

When using this component inside a Symfony application, certain objects and variables are automatically injected by Symfony so you can use them in your expressions (e.g. the request, the current user, etc.):

Caution

When using variables in expressions, avoid passing untrusted data into the array of variables. If you can't avoid that, sanitize non-alphanumeric characters in untrusted data to prevent malicious users from injecting control characters and altering the expression.

Caching

The ExpressionLanguage component provides a compile() method to be able to cache the expressions in plain PHP. But internally, the component also caches the parsed expressions, so duplicated expressions can be compiled/evaluated quicker.

The Workflow

Both evaluate() and compile() need to do some things before each can provide the return values. For evaluate(), this overhead is even bigger.

Both methods need to tokenize and parse the expression. This is done by the parse() method. It returns a ParsedExpression. Now, the compile() method just returns the string conversion of this object. The evaluate() method needs to loop through the "nodes" (pieces of an expression saved in the ParsedExpression) and evaluate them on the fly.

To save time, the ExpressionLanguage caches the ParsedExpression so it can skip the tokenization and parsing steps with duplicate expressions. The caching is done by a PSR-6 CacheItemPoolInterface instance (by default, it uses an ArrayAdapter). You can customize this by creating a custom cache pool or using one of the available ones and injecting this using the constructor:

1
2
3
4
5
use Symfony\Component\Cache\Adapter\RedisAdapter;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;

$cache = new RedisAdapter(...);
$expressionLanguage = new ExpressionLanguage($cache);

See also

See the The Cache Component documentation for more information about available cache adapters.

Using Parsed and Serialized Expressions

Both evaluate() and compile() can handle ParsedExpression and SerializedParsedExpression:

1
2
3
4
5
6
// ...

// the parse() method returns a ParsedExpression
$expression = $expressionLanguage->parse('1 + 4', []);

var_dump($expressionLanguage->evaluate($expression)); // prints 5
1
2
3
4
5
6
7
8
9
use Symfony\Component\ExpressionLanguage\SerializedParsedExpression;
// ...

$expression = new SerializedParsedExpression(
    '1 + 4',
    serialize($expressionLanguage->parse('1 + 4', [])->getNodes())
);

var_dump($expressionLanguage->evaluate($expression)); // prints 5

AST Dumping and Editing

It's difficult to manipulate or inspect the expressions created with the ExpressionLanguage component, because the expressions are plain strings. A better approach is to turn those expressions into an AST. In computer science, AST (Abstract Syntax Tree) is "a tree representation of the structure of source code written in a programming language". In Symfony, a ExpressionLanguage AST is a set of nodes that contain PHP classes representing the given expression.

Dumping the AST

Call the getNodes() method after parsing any expression to get its AST:

1
2
3
4
5
6
7
8
9
10
11
12
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;

$ast = (new ExpressionLanguage())
    ->parse('1 + 2', [])
    ->getNodes()
;

// dump the AST nodes for inspection
var_dump($ast);

// dump the AST nodes as a string representation
$astAsString = $ast->dump();

Manipulating the AST

The nodes of the AST can also be dumped into a PHP array of nodes to allow manipulating them. Call the toArray() method to turn the AST into an array:

1
2
3
4
5
6
7
// ...

$astAsArray = (new ExpressionLanguage())
    ->parse('1 + 2', [])
    ->getNodes()
    ->toArray()
;

Extending the ExpressionLanguage

The ExpressionLanguage can be extended by adding custom functions. For instance, in the Symfony Framework, the security has custom functions to check the user's role.

Note

If you want to learn how to use functions in an expression, read "The ExpressionLanguage Component".

Registering Functions

Functions are registered on each specific ExpressionLanguage instance. That means the functions can be used in any expression executed by that instance.

To register a function, use register(). This method has 3 arguments:

  • name - The name of the function in an expression;
  • compiler - A function executed when compiling an expression using the function;
  • evaluator - A function executed when the expression is evaluated.

Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;

$expressionLanguage = new ExpressionLanguage();
$expressionLanguage->register('lowercase', function ($str) {
    return sprintf('(is_string(%1$s) ? strtolower(%1$s) : %1$s)', $str);
}, function ($arguments, $str) {
    if (!is_string($str)) {
        return $str;
    }

    return strtolower($str);
});

var_dump($expressionLanguage->evaluate('lowercase("HELLO")'));
// this will print: hello

In addition to the custom function arguments, the evaluator is passed an arguments variable as its first argument, which is equal to the second argument of evaluate() (e.g. the "values" when evaluating an expression).

Using Expression Providers

When you use the ExpressionLanguage class in your library, you often want to add custom functions. To do so, you can create a new expression provider by creating a class that implements ExpressionFunctionProviderInterface.

This interface requires one method: getFunctions(), which returns an array of expression functions (instances of ExpressionFunction) to register:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
use Symfony\Component\ExpressionLanguage\ExpressionFunction;
use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface;

class StringExpressionLanguageProvider implements ExpressionFunctionProviderInterface
{
    public function getFunctions()
    {
        return [
            new ExpressionFunction('lowercase', function ($str) {
                return sprintf('(is_string(%1$s) ? strtolower(%1$s) : %1$s)', $str);
            }, function ($arguments, $str) {
                if (!is_string($str)) {
                    return $str;
                }

                return strtolower($str);
            }),
        ];
    }
}

Tip

To create an expression function from a PHP function with the fromPhp() static method:

1
ExpressionFunction::fromPhp('strtoupper');

Namespaced functions are supported, but they require a second argument to define the name of the expression:

1
ExpressionFunction::fromPhp('My\strtoupper', 'my_strtoupper');

You can register providers using registerProvider() or by using the second argument of the constructor:

1
2
3
4
5
6
7
8
9
10
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;

// using the constructor
$expressionLanguage = new ExpressionLanguage(null, [
    new StringExpressionLanguageProvider(),
    // ...
]);

// using registerProvider()
$expressionLanguage->registerProvider(new StringExpressionLanguageProvider());

Tip

It is recommended to create your own ExpressionLanguage class in your library. Now you can add the extension by overriding the constructor:

1
2
3
4
5
6
7
8
9
10
11
12
13
use Psr\Cache\CacheItemPoolInterface;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage as BaseExpressionLanguage;

class ExpressionLanguage extends BaseExpressionLanguage
{
    public function __construct(CacheItemPoolInterface $cache = null, array $providers = [])
    {
        // prepends the default provider to let users override it
        array_unshift($providers, new StringExpressionLanguageProvider());

        parent::__construct($cache, $providers);
    }
}
This work, including the code samples, is licensed under a Creative Commons BY-SA 3.0 license.
TOC
    Version