Quantcast
Channel: Symfony Blog
Viewing all articles
Browse latest Browse all 3058

New in Symfony 2.4: The ExpressionLanguage Component

$
0
0

Symfony 2.4 comes with a new component: ExpressionLanguage. ExpressionLanguage provides an engine that can compile and evaluate expressions.

The language it is just a strip-down version of Twig expressions. So, an expression is a one-liner that returns a value (mostly, but not limited to, Booleans).

Unlike Twig, ExpressionLanguage works in two modes:

  • compilation: the expression is compiled to PHP for later evaluation (note that the compiled PHP code does not rely on a runtime environment);
  • evaluation: the expression is evaluated without being first compiled to PHP.

To be able to compile an expression to a plain PHP string without the need for a runtime environment, the . operator calls must be explicit to avoid any ambiguities: foo.bar for object properties, foo['bar'] for array calls, and foo.getBar() for method calls.

Using the component is as simple as it can get:

1
2
3
4
5
6
7
8
9
useSymfony\Component\ExpressionLanguage\ExpressionLanguage;$language=newExpressionLanguage();echo$language->evaluate('1 + 1');// will echo 2echo$language->compile('1 + 2');// will echo "(1 + 2)"

The language supports everything Twig supports in expressions: math operators, strings, numbers, arrays, hashes, Booleans, ...

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 when compiling or evaluating:

1
2
3
$language->evaluate('a.b',array('a'=>newstdClass()));$language->compile('a.b',array('a'));

Last but not the least, you can easily extend the language via functions; they work in the same way as their Twig counterparts (see the register() method for more information.)

What about some use cases? Well, we were able to leverage the new component in many different other built-in Symfony components.

Service Container

You can use an expression anywhere you can pass an argument in the service container:

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

In the container, an expression has access to two functions: service() to get a service, and parameter to get a parameter value:

1
service("bar").getValue(parameter("value"))

Or in XML:

1
2
3
<serviceid="foo"class="Foo"><argumenttype="expression">service('bar').getvalue(parameter('value'))</argument></service>

There is no overhead at runtime as the PHP dumper uses the expression compiler; the previous expression is compiled to the following PHP code:

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

Access Control Rules

Configuring some security access control rules can be confusing, and this might lead to insecure applications.

The new allow_if setting simplifies the way you configure access control rules:

1
2
access_control:-{path:^/_internal/secure,allow_if:"'127.0.0.1'==request.getClientIp()orhas_role('ROLE_ADMIN')"}

This rule restricts the URLs starting with /_internal/secure to people browsing from localhost; request, token and user are the variables you have access to and is_anonymous(), is_authenticated(),is_fully_authenticated(), is_rememberme(), and has_role() are the functions defined in this context.

You can also use expressions in a Twig template by using the new expression function:

1
2
3
{%ifis_granted(expression('has_role("FOO")'))%}   ...{%endif%}

If you are using the SensioFrameworkExtraBundle, you also get a new annotation,@Security to secure controllers:

1
2
3
4
5
6
7
/** * @Route("/post/{id}") * @Security("has_role('ROLE_ADMIN')") */publicfunctionshowAction(Post$post){}

Note

The @Security annotation will be part of version 3 of the bundle, to be released before Symfony 2.4 final.

Caching

Version 3 of SensioFrameworkExtraBundle also comes with an enhanced @Cache annotation which gives you access to the HTTP validation caching model.

Instead of writing the same boilerplate code again and again for basic cases:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
/** * @Route("/post/{id}") * @Cache(smaxage="15") */publicfunctionshowAction(Request$request,Post$post){$response=newResponse();$response->setLastModified($post->getUpdated());if($response->isNotModified($request)){return$response;}// ...}

You can just configure everything in the annotation instead (that works for ETags as well):

1
2
3
4
5
6
7
8
/** * @Route("/post/{id}") * @Cache(smaxage="15", lastModified="post.getUpdatedAt()") */publicfunctionshowAction(Post$post){// ...}

Routing

Out of the box, Symfony can only match an incoming request based on some pre-determined variables (like the path info, the method, the scheme, ...), but some people want to be able to match on some more complex logic, based on other information of the Request.

To cover those more "dynamic" use cases, you can now use the condition setting, which allows you to add any valid expression by using the request and the routing context variables:

1
2
3
hello:path:/hello/{name}condition:"context.getMethod()in['GET','HEAD']andrequest.headers.get('User-Agent')=~'/firefox/i'"

Again, when using the URL matcher PHP dumper, there is no overhead at runtime as the condition is compiled to plain PHP:

1
2
3
4
5
6
7
// helloif(0===strpos($pathinfo,'/hello')&&preg_match('#^/hello/(?P<name>[^/]++)$#s',$pathinfo,$matches)&&(in_array($context->getMethod(),array(0=>"GET",1=>"HEAD"))&&preg_match("/firefox/i",$request->headers->get("User-Agent")))){return$this->mergeDefaults(array_replace($matches,array('_route'=>'hello')),array());}

Caution

Be warned that conditions are not taken into account when generating a URL.

Validation

The new Expression constraint lets you use an expression to validate a property:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
useSymfony\Component\Validator\ConstraintsasAssert;/** * @Assert\Expression("this.getFoo() == 'fo'", message="Not good!") */classObj{publicfunctiongetFoo(){return'foo';}}

In the expression, this references the current object being validated.

Business Rule Engine

Besides using the component in the framework itself, the expression language 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 ifuser.getGroup() in ['good_customers', 'collaborator']# Promote article to the homepage whenarticle.commentCount > 100 and article.category not in ["misc"]# Send an alert whenproduct.stock < 15

And that's the last post I'm going to publish about upcoming new features in Symfony 2.4. The next step will be the release of the first Symfony 2.4 release candidate in a few days.


Be trained by Symfony experts - 2013-11-18 Cologne - 2013-11-18 Cologne - 2013-11-18 Paris

Viewing all articles
Browse latest Browse all 3058

Trending Articles