symfony/symfony — pull request #8913 by fabpot (2013-09-02 12:15:19+00:00)
Title: New Component: Expression Engine

| Q | A
| ------------- | ---
| Bug fix? | no
| New feature? | yes
| BC breaks? | no
| Deprecations? | no
| Tests pass? | yes
| Fixed tickets | #8850
| License | MIT
| Doc PR | not yet

TODO:

- [ ] write documentation
- [ ] add tests for the new component
- [ ] implement expression support for access rules in the security component

The ExpressionEngine component provides an engine that can compile and
evaluate expressions.

It is a strip-down version of Twig (only the expression part of it is
implemented.) Like Twig, the expression is lexed, parsed, and
compiled/evaluated. So, it is immune to external injections by design.

If we compare it to Twig, here are the main big differences:

* only support for Twig expressions
* no ambiguity for calls (foo.bar is only valid for properties, foo['bar'] is only valid for array calls, and foo.bar() is required for method calls)
* no support for naming conventions in method calls (if the method is named getFoo(), you must use getFoo() and not foo())
* no notion of a line for errors, but a cursor (we are mostly talking about one-liners here)
* removed everything specific to the templating engine (like output escaping or filters)
* no support for named arguments in method calls
* only one extension point with functions (no possibility to define new operators, ...)
* and probably even more I don't remember right now

An open question is whether we keep the difference betweens arrays and hashes.

The other big difference with Twig is that it can work in two modes (possible
because of the restrictions described above):

* compilation: the expression is compiled to PHP and is self-sufficient (no need for a runtime engine)
* evaluation: the expression is evaluated without being compiled to PHP

Let's see a simple example:

```php
$engine = new ExpressionEngine();

echo $engine->evaluate('1 + 1');
// will echo 2

echo $engine->compile('1 + 2');
// will echo "(1 + 2)"
```

The engine supports:

* all basic math operators (with precedence rules):
* unary: not, -, +
* binary: or, and, b-or, b-xor, b-and, ==, !=, <, >, >=, <=, not in, in, .., +, -, ~, *, /, %, **

* all literals supported by Twig: strings, numbers, arrays (`[1, 2]`), hashes
(`{a: "b"}`), Booleans, and null.

* simple variables (`foo`), array accesses (`foo[1]`), property accesses
(`foo.bar`), and method calls (`foo.bar(1, 2)`).

* the ternary operator: `true ? true : false` (and all the shortcuts
implemented in Twig).

* function calls (`constant('FOO')` -- `constant` is the only built-in
functions).

* and of course, any combination of the above.

That's all folks!

I can see many use cases for this new component, and we have two use cases in
Symfony that we can implement right away.

The first one is expression support in the service container (it would replace
#8850) -- anywhere you can pass an argument in the service container, you can
use an expression:

$c->register('foo', 'Foo')->addArgument(new Expression('bar.getvalue()'));

You have access to the service container via `this`:

this.get("bar").getvalue(this.getParameter("value"))

The implementation comes with two functions that simplifies expressions
(`service()` to get a service, and `parameter` to get a parameter value). The
previous example can be simplified to:

service("bar").getvalue(parameter("value"))

Here is how to use it in XML:

```xml

foobar

```

and in YAML (I chose the syntax randomly ;)):

```yaml
parameters:
value: foobar

services:
bar:
class: Bar

foo:
class: Foo
arguments: ["#service("bar").getvalue(parameter("value"))"]
```

When using the container builder, Symfony uses the evaluator, but with the PHP
dumper, the compiler is used, and there is no overhead as the expression
engine is not needed at runtime. The expression above would be compiled to:

```php
$this->get("bar")->getvalue($this->getParameter("value"))
```

The second use case in Symfony would be for access rules, but I've not
implemented it yet (should be relatively trivial).