-
-
Notifications
You must be signed in to change notification settings - Fork 5.2k
/
Copy pathexpression_language.rst
419 lines (296 loc) · 14.6 KB
/
expression_language.rst
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
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