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
------------

.. code-block:: terminal

    $ composer require symfony/expression-language

.. include:: /components/require_autoload.rst.inc

.. _how-can-the-expression-engine-help-me:

How can the Expression Language Help Me?
----------------------------------------

The purpose of the component is to allow users to use expressions inside
configuration for more complex logic. For example, 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:

.. _component-expression-language-examples:

.. code-block:: text

    # 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 less vulnerable
to external injections because you must explicitly declare which variables are
available in an expression (but you should still sanitize any data given by end
users and passed to expressions).

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
:class:`Symfony\\Component\\ExpressionLanguage\\ExpressionLanguage`::

    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)

.. tip::

    See :doc:`/reference/formats/expression_language` to learn the syntax of
    the ExpressionLanguage component.

Null Coalescing Operator
........................

.. note::

    This content has been moved to the :ref:`null coalescing operator <component-expression-null-coalescing-operator>`
    section of ExpressionLanguage syntax reference page.

Parsing and Linting Expressions
...............................

The ExpressionLanguage component provides a way to parse and lint expressions.
The :method:`Symfony\\Component\\ExpressionLanguage\\ExpressionLanguage::parse`
method returns a :class:`Symfony\\Component\\ExpressionLanguage\\ParsedExpression`
instance that can be used to inspect and manipulate the expression. The
:method:`Symfony\\Component\\ExpressionLanguage\\ExpressionLanguage::lint`, on the
other hand, throws a :class:`Symfony\\Component\\ExpressionLanguage\\SyntaxError`
if the expression is not valid::

    use Symfony\Component\ExpressionLanguage\ExpressionLanguage;

    $expressionLanguage = new ExpressionLanguage();

    var_dump($expressionLanguage->parse('1 + 2', []));
    // displays the AST nodes of the expression which can be
    // inspected and manipulated

    $expressionLanguage->lint('1 + 2', []); // doesn't throw anything

    $expressionLanguage->lint('1 + a', []);
    // throws a SyntaxError exception:
    // "Variable "a" is not valid around position 5 for expression `1 + a`."

The behavior of these methods can be configured with some flags defined in the
:class:`Symfony\\Component\\ExpressionLanguage\\Parser` class:

* ``IGNORE_UNKNOWN_VARIABLES``: don't throw an exception if a variable is not
  defined in the expression;
* ``IGNORE_UNKNOWN_FUNCTIONS``: don't throw an exception if a function is not
  defined in the expression.

This is how you can use these flags::

    use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
    use Symfony\Component\ExpressionLanguage\Parser;

    $expressionLanguage = new ExpressionLanguage();

    // does not throw a SyntaxError because the unknown variables and functions are ignored
    $expressionLanguage->lint('unknown_var + unknown_function()', [], Parser::IGNORE_UNKNOWN_VARIABLES | Parser::IGNORE_UNKNOWN_FUNCTIONS);

.. versionadded:: 7.1

    The support for flags in the ``parse()`` and ``lint()`` methods
    was introduced in Symfony 7.1.

Passing in Variables
--------------------

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

    use Symfony\Component\ExpressionLanguage\ExpressionLanguage;

    $expressionLanguage = new ExpressionLanguage();

    class Apple
    {
        public string $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.):

* :doc:`Variables available in security expressions </security/expressions>`;
* :doc:`Variables available in service container expressions </service_container/expression_language>`;
* :ref:`Variables available in routing expressions <routing-matching-expressions>`.

.. _expression-language-caching:

Caching
-------

The ExpressionLanguage component provides a
:method:`Symfony\\Component\\ExpressionLanguage\\ExpressionLanguage::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 :method:`Symfony\\Component\\ExpressionLanguage\\ExpressionLanguage::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
:method:`Symfony\\Component\\ExpressionLanguage\\ExpressionLanguage::parse`
method. It  returns a :class:`Symfony\\Component\\ExpressionLanguage\\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 :class:`Symfony\\Component\\Cache\\Adapter\\ArrayAdapter`). You can
customize this by creating a custom cache pool or using one of the available
ones and injecting this using the constructor::

    use Symfony\Component\Cache\Adapter\RedisAdapter;
    use Symfony\Component\ExpressionLanguage\ExpressionLanguage;

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

.. seealso::

    See the :doc:`/components/cache` documentation for more information about
    available cache adapters.

Using Parsed and Serialized Expressions
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

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

    // ...

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

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

.. code-block:: php

    use Symfony\Component\ExpressionLanguage\SerializedParsedExpression;
    // ...

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

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

.. _expression-language-ast:

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, an ExpressionLanguage AST is a set of
nodes that contain PHP classes representing the given expression.

Dumping the AST
~~~~~~~~~~~~~~~

Call the :method:`Symfony\\Component\\ExpressionLanguage\\ExpressionLanguage::getNodes`
method after parsing any expression to get its AST::

    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 :method:`Symfony\\Component\\ExpressionLanguage\\ExpressionLanguage::toArray`
method to turn the AST into an array::

    // ...

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

.. _expression-language-extending:

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
    ":ref:`component-expression-functions`".

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
:method:`Symfony\\Component\\ExpressionLanguage\\ExpressionLanguage::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::

    use Symfony\Component\ExpressionLanguage\ExpressionLanguage;

    $expressionLanguage = new ExpressionLanguage();
    $expressionLanguage->register('lowercase', function ($str): string {
        return sprintf('(is_string(%1$s) ? strtolower(%1$s) : %1$s)', $str);
    }, function ($arguments, $str): string {
        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).

.. _components-expression-language-provider:

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
:class:`Symfony\\Component\\ExpressionLanguage\\ExpressionFunctionProviderInterface`.

This interface requires one method:
:method:`Symfony\\Component\\ExpressionLanguage\\ExpressionFunctionProviderInterface::getFunctions`,
which returns an array of expression functions (instances of
:class:`Symfony\\Component\\ExpressionLanguage\\ExpressionFunction`) to
register::

    use Symfony\Component\ExpressionLanguage\ExpressionFunction;
    use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface;

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

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

.. tip::

    To create an expression function from a PHP function with the
    :method:`Symfony\\Component\\ExpressionLanguage\\ExpressionFunction::fromPhp` static method::

        ExpressionFunction::fromPhp('strtoupper');

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

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

You can register providers using
:method:`Symfony\\Component\\ExpressionLanguage\\ExpressionLanguage::registerProvider`
or by using the second argument of the constructor::

    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::

        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);
            }
        }

.. _`AST`: https://en.wikipedia.org/wiki/Abstract_syntax_tree
.. _`CacheItemPoolInterface`: https://github.com/php-fig/cache/blob/master/src/CacheItemPoolInterface.php
