Utiliser le composant Expression Language de Symfony2 dans les fichiers de services YML et XML

Tout dans Symfony2 tourne autour du composant Injection de Dépendance et donc des services que l'on déclare dedans. Pour cela on utilise généralemnt des fichiers XML ou Yaml. Très pratique, ces 2 formats sont tout de même frustant car, de par leur nature, totalement statique. Ce qui oblige certaines fois à augmenter la compléxité en rajoutant des factory ou autre services, ou encore en injectant un service "trop haut niveau". Apparue dans Symfony 2.4, le composant Expression Language permet de résoudre certains de ces problèmes en ajoutant du dynamisme à nos fichiers statiques.

Son utilisation est plutôt simple et les possibilités très intéressantes, dommage que les exemples de code soient déciminés dans la doc de sf2. Je vous propose donc un petit tour d'horizon de ce que l'on peut faire avec.

La syntaxe

Dans les fichiers Yaml, une expression commence par "@=". Dans les fichiers XML il faut utiliser le type "expression" dans le tag "argument".

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


Différentes variables permettent d'accéder au container, à un paramètre ou encore à un service.

# retourne la valeur du paramètre acme
@=parameter('acme')
 
# retourne le service acme équivalent à @acme
@=service('acme')
 
# retourne le container
@=container
 
# retourne si le paramètre acme existe dans le container
@=container.hasParameter('acme')

 

Utilisation de constante

Plutôt que passer une valeur en dur dans un construteur, l'utilisation d'une constante peut être bien plus pratique.

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

Notez que les "\" sont en triple à chaque fois, ce n'est pas une erreure. Cette declaration donnera dans le 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 et sf3.1 il semble que les "\" doivent maintenant être quadruplé, depuis mon upgrade de version j'ai du corriger mes YAML en ajoutant un "\" de plus sous peine d'avoir le message d'erreur: " Found unknown escape character "\C" at line 191"

EDIT: SF3.2 une nouvelle notation native permet d'appeler les constantes plus simplement:
 

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

 

Appel de méthode

On peut appeler une méthode d'un autre service, comme un factory ce qui donne:

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

Une autre utilisation classique dans Symfony2 c'est l'injection d'un repository Doctrine2 sans avoir à passer le service doctrine et ainsi respecter la loi de Demeter.

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

ce qui donnera ce code dans le 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"));
}

 

Utilisation de condition

On peut aller encore plus loin en utilisant des conditions pour injecter tel ou tel service.

parameters:
    acme.debug: true

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

Ce qui va donner dans le container:

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

Attention tout de même à ne pas déplacer trop de logique dans les fichiers de configuration, il n'y a pas de test unitaire dessus.

Il y a un commentaire.

Ecrit par Med le 25 janv. 2018

Dans la configuration suivante s'il n'existe pas un parametre défini avec le nom "some_param" dans le container alors une array vide sera injecté my_service: class: App\YourBundle\Service\Myservice arguments: ["@=container.hasParameter('some_param') ? parameter('some_param') : []"] Dans cette 2ème configuration s'il n'existe pas un parametre défini avec le nom "some_param" dans le container alors une simple array contenant value1 et value2 sera injecté my_service: class: App\YourBundle\Service\Myservice arguments: ["@=container.hasParameter('some_param') ? parameter('some_param') : ['value1', 'value2']"] Dans cette 3ème configuration s'il n'existe pas un parametre défini avec le nom "some_param" dans le container alors une array avec clé/valeur sera injecté my_service: class: App\YourBundle\Service\Myservice arguments: ["@=container.hasParameter('some_param') ? parameter('some_param') : {'key1': 'value1', 'key2': 'value2'}"]

Ajouter un commentaire