Aller au menu - Aller au contenu

Icône Les services, utilisation poussée

Avatar
Mise à jour : 09/02/2012
Difficulté : Intermédiaire Intermédiaire Durée d'étude : 2 heures
23 763 visites depuis 7 jours, dont 278 sur ce chapitre classé 14/778
Cette deuxième partie est réservé aux fonctionnalités les plus intéressantes des services. Maintenant que les bases vous sont acquises, nous allons pouvoir découvrir des fonctionnalités très puissantes de Symfony.
Sommaire du chapitre :
Icône du chapitre
Chapitre précédent Sommaire Chapitre suivant

Les arguments

La plupart du temps, vos services ne fonctionneront pas seuls, et vont nécessiter l'utilisation d'autres services, de paramètres ou de variables. Il a donc fallu trouver un moyen propre et efficace pour palier à ce problème. Pour passer des arguments à votre service, il faut utiliser le fichier de configuration :

Code : Autre
1
2
3
4
5
6
parameters:

services:
    sdz_blog.antispam:
        class: Sdz\BlogBundle\Service\SdzAntispam
        arguments: [] # Liste des arguments


Les arguments peuvent être :
  • Des valeurs normales en YAML (des booléens, des chaines de caractères, des nombres, etc.)
  • Des paramètres (définis dans le parameters.ini par exemple) : l'identifiant du paramètre est encadré de signes % : %parametre_id%
  • Des services : l'identifiant du service est précédé d'un arobase : @service_id


Par exemple :

Code : Autre
1
2
3
4
5
6
parameters:

services:
    sdz_blog.antispam:
        class: Sdz\BlogBundle\Service\SdzAntispam
        arguments: [@doctrine, %locale%, 3]

Dans cet exemple, notre service utilise
  • @doctrine : le service Doctrine (pour utiliser la base de données),
  • %locale% : le paramètre locale (pour récupérer la langue),
  • 3 : et le nombre 3 (qu'importe son utilité !).

Une fois vos arguments définis dans la configuration, il vous suffit de les récupérer avec votre constructeur. Les arguments de la configuration et le constructeur vont donc de paire. Si vous modifiez l'un, n'oubliez pas d'adapter l'autre. Voici donc le constructeur adapté à notre nouvelle configuration :

Code : PHP
 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
<?php
// src/Sdz/BlogBundle/Service/SdzAntispam.php

namespace Sdz\BlogBundle\Service;

use Symfony\Bundle\DoctrineBundle\Registry;

/**
 * Un anti-spam simple pour Symfony2.
 *
 * @author Leglopin
 */
class SdzAntispam
{
    /**
     * @var Symfony\Bundle\DoctrineBundle\Registry
     */
    protected $doctrine;
    
    protected $locale;
    
    protected $nbFoundedForSpam;
    
    public function __construct(Registry $doctrine, $locale, $nbFoundedForSpam)
    {    	
        $this->doctrine = $doctrine;
        $this->locale = (string) $locale;
        $this->nbFoundedForSpam = (int) $nbFoundedForSpam;
    }

    public function isSpam($text)
    {
        if( ($this->countLinks($text) + $this->countMails($text)) >= $this->nbFoundedForSpam ) // Nous utilisons ici l'argument $nbFoundedForSpam
        {
            return true;
        }
        else
        {
            return false;
        }
    }
    
    // ...
}


Vous pouvez remarquer que les arguments permettent de transmettre des services à d'autres services : c'est pour cette raison qu'on appelle ce concept l'injection de dépendance (dependency injection en anglais). Au lieu de faire appel directement dans le code à des classes, on fait appel à un conteneur de services qui crée alors des dépendances dynamiques :) . La boucle est bouclée !

Les calls

Cette fonctionnalité est assez simple : elle permet d'appeler directement après la construction du service certaines fonctions.

Ainsi, pour faire exactement la même chose que précédemment, vous pouvez configurer votre service comme ceci :

Code : Autre
1
2
3
4
5
6
7
8
parameters:

services:
    sdz_blog.antispam:
        class: Sdz\BlogBundle\Service\SdzAntispam
        arguments: [@doctrine, 3]
        calls:
            - [ setLocale, [ %locale% ] ]


Vous pouvez définir plusieurs calls, en rajoutant des lignes à tiret. Le premier argument (ici, setLocale) est le nom de la méthode à exécuter. Le deuxième argument (ici, [ %locale% ]) est le tableau des arguments à transmettre à la méthode exécutée.

En adaptant bien sûr le service comme ceci :

Code : PHP
 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
<?php
// src/Sdz/BlogBundle/Service/SdzAntispam.php

namespace Sdz\BlogBundle\Service;

/**
 * Un anti-spam simple pour Symfony2.
 *
 * @author Leglopin
 */
class SdzAntispam
{
    protected $doctrine;
    
    protected $locale;
    
    protected $nbFoundedForSpam;
    
    public function __construct(Registry $doctrine, $nbFoundedForSpam)
    {
        $this->doctrine = $doctrine;
    	$this->nbFoundedForSpam = $nbFoundedForSpam;
    }
    
    // C'est cette méthode qui va être exécutée par le call
    public function setLocale($locale)
    {
        $this->locale = $locale;
    }
    
    // ...
}


L'utilité des calls est surtout remarquable pour l'intégration des libraires externes (Zend Framework, Geshi, etc.).

Les tags

Une fonctionnalité très importante des services est la possibilité d'utiliser les tags. Les tags sont en fait des options ajoutées à un service pour lui donner la capacité d'intervenir différemment avec Symfony. Par exemple, il existe un tag twig.extension qui signale à Symfony que le service est une extension Twig.

Nous allons ici parler des tags les plus importants, ainsi que comment les mettre en place.

Extension Twig



Pour l'exemple, nous allons transformer notre SdzAntispam pour lui ajouter la possibilité de vérifier un texte de la vue. Adaptons la configuration du service :

Code : Autre
1
2
3
4
5
6
7
parameters:

services:
    sdz_blog.antispam:
        class: Sdz\BlogBundle\Service\SdzAntispam
        tags:
            -  { name: twig.extension }


Notre service sera donc traité comme une extension Twig. Cependant, un service implantant ce tag doit étendre Twig_Extension. Adaptons le code du service en implémentant les méthodes getFunctions() et getName() obligatoire :

Code : PHP
 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
<?php
// src/Sdz/BlogBundle/Service/SdzAntispam.php

namespace Sdz\BlogBundle\Service;

/**
 * Un anti-spam simple pour Symfony2.
 *
 * @author Leglopin
 */
class SdzAntispam extends \Twig_Extension
{
    // La méthode getName(), obligatoire
    public function getName()
    {
        return 'SdzAntispam';
    }
	
    // La méthode getFunctions(), qui retourne un tableau avec les fonctions qui peuvent être appelées depuis cette extension
    public function getFunctions()
    {
        return array(
            'antispam_check' => new \Twig_Function_Method($this, 'isSpam') 
        );

        // 'antispam_check' est le nom de la fonction qui sera disponible sous Twig
        // 'new \Twig_Function_Method($this, 'isSpam') ' est la façon de dire que cette fonction va exécuter notre méthode isSpam ci-dessous
    }
	
    /**
     * Vérifie si le texte est un spam ou non.
     * Un texte est considéré comme spam à partir de 3 liens
     * ou adresses e-mails dans son contenu.
     * 
     * @param string $text
     */
    public function isSpam($text)
    {
        if( ($this->countLinks($text) + $this->countMails($text)) >= 3 )
        {
            return true;
        }
        else
        {
            return false;
        }
    }
    
    // ...
}


Vous pouvez maintenant utiliser la fonction dans Twig :

Code : Autre
1
{{ antispam_check('Texte') }}


Pour plus d'informations à propos de la création d'extensions pour Twig, lisez ceci : http://twig.sensiolabs.org/doc/extensions.html.

Les évènements du cœur


Les services peuvent être utilisés avec le gestionnaire d'évènements. Un chapitre sera bientôt disponible à ce sujet. Vous pouvez en attendant lire la documentation à ce sujet.

Les types de champs de formulaire


Cette fonctionnalité est très pratique, mais comme elle n'est pas documentée, je ne connais que ce que j'ai découvert par moi-même, et il se peut qu'il manque quelques fonctionnalités qui pourraient être intéressantes. N'hésitez pas à faire des recherches ;) .

Pour déclarer un type de champ, il faut utiliser le tag form.type et indiquer un alias. Par exemple, pour un champ ckeditor (un éditeur WYSIWWYG) :

Code : Autre
1
2
3
4
5
services:
    sdz_blog.ckeditor:
        class:        Sdz\BlogBundle\Form\Extension\CkeditorType
        tags:
            - { name: form.type, alias: ckeditor }


La classe CkeditorType contient alors :

Code : PHP
 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
<?php
// src/Sdz/BlogBundle/Form/Extension/CkeditorType.php

/**
 * Type de champ de formulaire pour CKEditor.
 *
 * @author Leglopin
 */
namespace Sdz\BlogBundle\Form\Extension;

use Symfony\Component\Form\AbstractType;

class CkeditorType extends AbstractType
{
    public function getParent(array $options)
    {
        return 'textarea';
    }

    public function getName()
    {
        return 'ckeditor';
    }
    
    public function getDefaultOptions(array $options)
    {    	
    	$defaultOptions = parent::getDefaultOptions($options);
    	$defaultOptions['attr']['class'] = 'ckeditor';
    	
    	return $defaultOptions;
    }
}


Vous venez de déclarer le type ckeditor (nom de l'alias). Ce type hérite de toutes les fonctionnalités d'un textarea (grâce à la méthode getParent()) tout en disposant de la classe CSS ckeditor (définie dans la méthode getDefaultOptions()) vous permettant, en ajoutant CKEditor à votre site, de transformer vos textarea en éditeur WYSIWYG. Pour l'utiliser, modifiez vos FormType pour utiliser 'ckeditor' à la place de 'textarea'. Par exemple, dans notre ArticleType :

Code : PHP
 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
<?php
namespace Sdz\BlogBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;

class ArticleType extends AbstractType
{
    public function buildForm(FormBuilder $builder, array $options)
    {
        $builder->add('date')
                ->add('titre')
                
                // On remplace simplement le type textarea par le type ckeditor
                ->add('contenu', 'ckeditor')
                
                ->add('pseudo')
                ->add('tags', 'collection', array(
                    'type' => new TagType,
                    'allow_add' => true,
                    'allow_delete' => true
                ));
    }
    
    public function getName()
    {
        return 'sdz_blogbundle_articletype';
    }
    
    public function getDefaultOptions(array $options)
    {
        return array(
            'data_class' => 'Sdz\BlogBundle\Entity\Article',
        );
    }
}


Pour en savoir plus, vous pouvez regarder le fichier vendor\symfony\src\Symfony\Component\Form\AbstractType.php.

Les autres tags


Il existe beaucoup d'autres tags. Vous pouvez trouver la liste des tags par défaut dans Symfony ici : http://symfony.com/doc/2.0/reference/dic_tags.html, mais il faut savoir que cette liste n'est pas complète car d'autres bundles ajoutent leur propre tags ;) .

La liste officielle n'étant pas très pratique pour comprendre comment utiliser certains tags, je compte faire ici une liste avec une description plus détaillée de chacun d'entre eux :) :

Identifiant Description Exemple
templating.engine Ce tag indique à Symfony que votre classe permet d'utiliser un nouveau moteur de templates. Par exemple, Twig possède sa classe TwigEngine. Pour déclarer un moteur de templates, utilisez le tag templating.engine.

Vous devez ensuite créer votre classe Engine sur le modèle de l'interface EngineInterface
Code : Autre
1
2
3
4
5
services:
    templating.engine.your_engine_name:
        class: Fully\Qualified\Engine\Class\Name
        tags:
            - { name: templating.engine }
templating.helper Utilise votre service en tant que Helper de vue. Un helper de vue est différent d'une extension Twig car il n'est utilisable que dans les templates PHP. Vous devez définir un alias pour utiliser ce dernier dans votre vue.
Une fois déclaré, vous pouvez accéder à votre helper ainsi : <?php $view['alias_name']; ?>, soit par exemple pour SdzAntispam : <?php $view['antispam']->isSpam(/* ... */); ?>
Code : Autre
1
2
3
4
5
services:
    templating.helper.your_helper_name:
        class: Fully\Qualified\Helper\Class\Name
        tags:
            - { name: templating.helper, alias: alias_name }
routing.loader Utilise votre service en tant que classe de chargement de routes. Cette fonctionnalité permet d'ajouter le support de plus de formats de configuration de routes. Par exemple, il existe des loaders PhpFileLoader, XmlFileLoader et YamlFileLoader. Pour déclarer votre classe de chargement, utilisez le tag routing.loader.
Votre classe de chargement doit correspondre à l'interface ci-contre :
Code : Autre
1
2
3
4
5
services:
    routing.loader.your_loader_name:
        class: Fully\Qualified\Loader\Class\Name
        tags:
            - { name: routing.loader }

Interface :
Secret (cliquez pour afficher)
Code : PHP
 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
<?php
interface RouterLoader
{
    /**
     * Charge les routes depuis une ressource.
     * Cette méthode doit retourner une collection RouteCollection.
     *
     * @param mixed  $string La ressource
     * @param string $type Le type de ressource
     *
     * @return RouteCollection
     *
     * @api
     */
    public function load($string, $type = null);

    /**
     * Cette méthode est utilisée pour savoir si le Loader est capable de charger la ressource.
     *
     * @param mixed  $resource La ressource
     * @param string $type     Le type de ressource
     *
     * @return Boolean Retourne TRUE si il est possible d'utiliser ce Loader, FALSE sinon
     *
     * @api
     */
    public function supports($string, $type = null);
}
translation.loader Utilise votre service en tant que classe de chargement de traductions, sur le même principe que routing.loader. Cette fonctionnalité permet d'ajouter le support de plus de formats de configuration de traductions. Par exemple, il existe un loader YamlFileLoader. Pour déclarer votre classe de chargement, utilisez le tag translation.loader avec un alias qui doit correspondre à l'extension que peut comprendre votre loader.
Votre classe de chargement doit correspondre à l'interface LoaderInterface.
Code : Autre
1
2
3
4
5
services:
    routing.loader.your_loader_name:
        class: Fully\Qualified\Loader\Class\Name
        tags:
            - { name: translation.loader, alias: ini }

Les services courants de Symfony2

Les services courants de Symfony



Certains d'entre vous ont peut-être déjà remarqué qu'ils avaient déjà utilisé des services auparavant. En effet, Symfony fournit par défaut par mal de services, dont appartiennent par exemple Doctrine, Request ou Swiftmailer. Je vous propose d'étudier plus en détails certains services qui pourront vous être très utiles par la suite !

Identifiant Description
doctrine.orm.entity_manager Ce service est l'instance de l'EntityManager de Doctrine ORM. Vous avez probablement déjà utilisé Doctrine en tant que service, mais peut-être sans le savoir car la classe Controller définit une méthode appelée getDoctrine() qui renvoi le service directement. Récupérer l'EntityManager dans un service est extrêmement pratique pour ensuite effectuer des opérations en base de données, récupérer un repository, etc.
event_dispatcher Ce service donne accès à l'event dispatcher. Cet élément est un peu trop complexe pour le réduire à ces quelques lignes, mais disons que c'est un objet pour gérer les évènements et les fonctions qui réagissent à ces évènements. Un chapitre sur le gestionnaire d'évènements est en cours de rédaction ;) .
kernel Ce service vous donne accès au coeur de Symfony. Grâce à lui, vous pouvez localiser des bundles, récupérer le chemin de base du site, etc. Voyez le fichier Kernel.php pour connaitre toutes les possibilités.
logger Ce service est la classe de logs. Grâce à lui, vous pouvez utiliser des fichiers de logs très simplement. Symfony utilise Monolog pour gérer ses logs. Voyez les fichiers vendor\symfony\src\Symfony\Bridge\Monolog\Logger.php et vendor\monolog\src\Monolog\Logger.php pour en savoir plus.
mailer Ce service vous renvoi par défaut une instance de SwiftMailer, vous permettant d'envoyer des e-mails. Vous pouvez consulter la documentation de SwiftMailer pour en savoir plus.
request Ce service est très important : il vous donne une instance de Request qui représente la requête du client. Vous pouvez par exemple :
  • Récupérer la session en cours : <?php $this->get('request')->getSession()
  • Récupérer l'IP du client : <?php $this->get('request')->getClientIp()
  • Récupérer la méthode de la requête (comme dans les formulaires) : <?php $this->get('request')->getMethod() : POST, GET, PUT, etc.
  • Savoir si c'est une requête en AJAX : <?php $this->get('request')->isXmlHttpRequest()
  • Et bien d'autres choses ...

Je vous laisse découvrir tout ceci : Symfony\Component\HttpFoundation\Request.php
router Ce service vous donne accès au routeur (Symfony\Component\Routing\Router). C'est cet objet qui génère vos routes et qui les transforme en URL. Vous pouvez par exemple générer une route :

Code : PHP
1
2
<?php
$this->get('router')->generate('homepage');


Ou trouver une route correspondant à une URL :

Code : PHP
1
2
<?php
$this->get('router')->match($url);


La classe Controller a aussi un raccourci pour générer une route : vous pouvez générer une URL dans un contrôleur avec <?php $this->generateUrl($nomRoute, $parametres, $estAbsolu);
security.context Ce service est surtout utile car il permet de récupérer l'utilisateur courant :

Code : PHP
1
2
<?php
$this->get('security.context')->getToken()->getUser();


Ce code fonctionne seulement si l'utilisateur est connecté. Pour le vérifier, il faut vérifier que <?php $this->get('security.context')->getToken(); n'est pas nul.
service_container Ce service vous renvoi le service container. Comme vous pouvez vous en douter, on ne l'utilise pas dans les contrôleurs. Il est surtout utile dans les autres services, où on le passe en argument pour accéder au conteneur ;) .
session Ce service représente les sessions. Voyez le fichier Symfony\Component\HttpFoundation\Session.php pour en savoir plus.
twig Ce service représente une instance de Twig_Environment. Il permet d'afficher une vue. Vous pouvez en savoir plus en lisant la documentation de Twig. Ce service peut être utile pour modifier l'environnement de Twig depuis l’extérieur (lui ajouter des extensions, etc.).
templating Ce service représente le moteur de templates. Cela peut être Twig, PHP ou tout autre moteur utilisable. Ce service montre l'intérêt de l'injection de dépendances : on réussit à faire un code valide pour plusieurs moteurs de templates en même temps, et changer de moteur ne nécessite pas de modifier le code.

La classe Controller fait référence à ce service pour afficher une vue. Quand vous utilisez la méthode $this->render() d'un contrôleur, vous utilisez en fait le service templating. Voyez vous-même le contenu de cette méthode render() :

Code : PHP
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<?php
// vendor\symfony\src\Symfony\Bundle\FrameworkBundle\Controller\Controller.php

/**
 * Renders a view.
 *
 * @param string   $view The view name
 * @param array    $parameters An array of parameters to pass to the view
 * @param Response $response A response instance
 *
 * @return Response A Response instance
 */
public function render($view, array $parameters = array(), Response $response = null)
{
	return $this->container->get('templating')->renderResponse($view, $parameters, $response);
}
J'espère avoir réussi à vous éclairer à propos de la gestion des services dans Symfony.

Vous pouvez maintenant vous rendre compte que grâce à eux, de nouvelles possibilités s'offrent à vous : les extensions Twig, l'intégration de bibliothèque tierces, etc.

Ces deux chapitres sur les services ont été écrits à l'origine par Titouan Galopin (Leglopin).
Chapitre précédent Sommaire Chapitre suivant

Partager

Il n'y a pas encore de commentaire pour ce tuto.