Use the Symfony Expression Language component in YML and XML service files

Everything in Symfony revolves around the Dependency Injection component and therefore the services that we declare in it. For this, XML or Yaml files are generally used. Very practical, these 2 formats are still frustrating because, by their nature, totally static. This sometimes requires increasing complexity by adding factories or other services, or by injecting a “too high level” service. First introduced in Symfony 2.4, the Expression Language component solves some of these problems by adding dynamism to our static files.

Its use is rather simple and the possibilities very interesting, too bad the code examples are decimated in the sf2 doc. So I offer you a short overview of what you can do with it.

The syntax

In Yaml files, an expression starts with “@=”. In XML files you have to use the “expression” type in the “argument” tag.

#YML:
@=parameter('acme')
 
#XML
<argument type="expression">parameter('acme')</argument>

Different variables allow access to the container, a parameter or even a service.

# return value of acme parameter
@=parameter('acme')
 
# retunr @acme service
@=service('acme')
 
# return the container
@=container
 
# return if the parameter exists
@=container.hasParameter('acme')

Use of constant


Rather than passing a hard value into a constructor, using a constant can be much more practical.

acme.provider.company.from_client:
    class: "%acme.provider.company.from_client.class%"
    arguments:
       - "@=constant('\\\Acme\\\\AcmeBundle\\\Provider\\\FromInterface::CLIENT')"

Note that the “\” are in triplicate each time, it's not a mistake. This statement will give in the container:

protected function getAcme_Provider_Company_From_ClientService()
{
        return $this->services['acme.provider.company.from_client'] = new \Acme\AcmeBundle\Provider\FromClient(constant("\\Acme\\AcmeBundle\\Provider\\FromInterface::CLIENT"));
}


EDIT: sf3.0 and sf3.1 it seems that the “\” must now be quadrupled, since my version upgrade I had to correct my YAML by adding one more “\” under penalty of getting the error message: “Found unknown escape character “\ C” at line 191"

EDIT: SF3.2 a new native notation makes it easier to call constants:

# app/config/services.yml
services:
    app.my_service:
        # ...
        arguments:
            - !php/const:AppBundle\Entity\BlogPost::MUM_ITEMS
            - !php/const:PHP_INT_MAX

Method call

You can call a method from another service, such as a factory, which gives:

acme.provider.company.from_client:
    class: "%acme.provider.company.from_client.class%"
    arguments:
        - "@=service('factory').create()"

Another classic use in Symfony is the injection of a Doctrine2 repository without having to pass the doctrine service and thus respect Demeter's law.

acme.manager.repository_user:
    class: "%acme.manager.repository_user.class%"
    arguments:
        - "@=service('doctrine').getRepository('AcmeBundle:User')"
 

which will give this code in the container:

protected function getAcme_Manager_Repository_UserService()
{
    return $this->services['acme.manager.repository_user'] = new \Acme\AcmeBundle\Manager\RepositoryUser($this->get("doctrine")->getRepository("AcmeBundle:User"));
}

Use of condition

We can go even further by using conditions to inject this or that service.

parameters:
    acme.debug: true

services:
    acme.provider:
        class: "%acme.provider.class%"
        arguments:
            - "@=parameter('acme.debug') ? service('logger') : null"

What will look like in the container:

protected function getAcme_ProviderService()
{
    return $this->services['acme.provider'] = new \Acme\AcmeBundle\Provider((($this->getParameter("acme.debug")) ? ($this->get("logger")) : (null)));
}


Be careful not to move too much logic in the configuration files, there is no unit test on it.

Add a comment