Je suis développeur web freelance et propose des formations à Symfony2 ! Contactez-moi pour en discuter.

Cet article est la traduction d’un article original de Fabien Potencier, à l’origine de Symfony2, disponible ici.

Dans le précédent article de cette série, nous avions vidé la classe Simplex\Framework en étendant la classe HttpKernel du composant éponyme. En voyant cette classe vide, vous pouvez être tenté de déplacer du code qui se trouve dans le contrôleur de façade dedans :


addSubscriber(new HttpKernel\EventListener\RouterListener($matcher));
$dispatcher->addSubscriber(new HttpKernel\EventListener\ResponseListener('UTF-8'));

parent::__construct($dispatcher, $resolver);
}
}

Le code du contrôleur de façade devient alors plus conçis :


handle($request)->send();

Avoir un contrôleur de façace concis vous permet d’avoir plusieurs contrôleurs de façades dans votre application. En quoi est-ce utile ? Cela permet d’avoir différentes configurations pour les environnements de développement et de production par exemple. Dans l’environnement de développement, il est utile d’activer l’affichage des erreurs dans le navigateur pour faciliter le débuggage :


ini_set('display_errors', 1);
error_reporting(-1);

… Mais vous ne voudrez certainement pas la même configuration dans l’environnement de production (car afficher le résultat des erreurs pour les utilisateurs est une faille de sécurité que peuvent exploiter les plus malicieux). Avoir 2 contrôleurs de façade différents vous donne l’opportunité d’avoir des configuration légèrement différentes pour chacune d’entre elles.

Déplacer du code du contrôleur de façade vers la classe de framework rend notre framework plus configurable, mais en même temps, crée des problèmes :

  • Nous ne sommes plus capable d’enregisitrer des écouteurs personnalisés, car le dispatcher n’est plus disponible à l’extérieur de la classe Framework (cela peut facilement être contourné en ajoutant une méthode Framework::getEventDispatcher()).
  • Nous avons perdu la flexibilité que nous avions avant : vous ne pouvez pas changer l’implémentation de l’UrlMatcher ou du ControllerResolver
  • C’estlié au point précédent, il est plus possible de tester facilement notre framework car il est impossible d’en mocker les objets internes
  • Nous ne pouvons plus changer le charset fourni au ResponseListener (On peut le contourner en ajoutant un argument au constructeur)

Le code précédent ne présentait pas les mêmes problèmes car nous utilisions l’injection de dépendances; toutes les dépendances de nos objets étaient injectées dans leurs constructeurs (par exemple, les dispatchers d’évènements étaient injectés dans le framework de telle sorte que nous ayions un contrôle totalt sur sa création et sa configuration).

Est-ce que cela signifie que nous devons faire un choix entre flexibilité, personnalisation, facilité à tester et non-copie de code identique des contrôleurs de façade dans les applications ? Comme vous vous en doutez, il y a une solution. Nous pouvons résoudre tous ces problèmes en utilisant le container d’injection de dépendances de Symfony2 :


{
"require": {
"symfony/class-loader": "2.1.*",
"symfony/http-foundation": "2.1.*",
"symfony/routing": "2.1.*",
"symfony/http-kernel": "2.1.*",
"symfony/event-dispatcher": "2.1.*",
"symfony/dependency-injection": "2.1.*"
},
"autoload": {
"psr-0": { "Simplex": "src/", "Calendar": "src/" }
}
}

Créez un nouveau fichier pour accueillir la configuration du container d’injection de dépendaces :


register('context', 'Symfony\Component\Routing\RequestContext');
$sc->register('matcher', 'Symfony\Component\Routing\Matcher\UrlMatcher')
->setArguments(array($routes, new Reference('context')))
;
$sc->register('resolver', 'Symfony\Component\HttpKernel\Controller\ControllerResolver');

$sc->register('listener.router', 'Symfony\Component\HttpKernel\EventListener\RouterListener')
->setArguments(array(new Reference('matcher')))
;
$sc->register('listener.response', 'Symfony\Component\HttpKernel\EventListener\ResponseListener')
->setArguments(array('UTF-8'))
;
$sc->register('listener.exception', 'Symfony\Component\HttpKernel\EventListener\ExceptionListener')
->setArguments(array('Calendar\\Controller\\ErrorController::exceptionAction'))
;
$sc->register('dispatcher', 'Symfony\Component\EventDispatcher\EventDispatcher')
->addMethodCall('addSubscriber', array(new Reference('listener.router')))
->addMethodCall('addSubscriber', array(new Reference('listener.response')))
->addMethodCall('addSubscriber', array(new Reference('listener.exception')))
;
$sc->register('framework', 'Simplex\Framework')
->setArguments(array(new Reference('dispatcher'), new Reference('resolver')))
;

return $sc;

Le but de ce fichier est de configurer vos objets et leurs dépendances. Rien n’est instancié durant cette phase de configuration. Il s’agit pûrement d’une description statique des objets que vous devez manipuler et comment les créer. Les objets seront créés à la demande lorsque vous y accéderez ou lorsque le container devra créer d’autres objets.

Par exemple, pour créer l’écouter du routeur, nous disons à Symfony que son nom de classe est Symfony\Component\HttpKernel\EventListener\RouterListener, et que son constructeur prend en arguement un objet matcher (new Reference(‘matcher’)). Comme vous pouvez le voir, chaque objet est référencé par un nom, une chaine qui identifie chaque objet de manière unique. Ce nom nous permets d’obtenir un objet et de le référencer dans les définitions d’autres objets.

Par défaut, à chaque fois que vous récupérez un objet depuis le container, il renvoit la même instance. C’est car un container gère vos objets « globaux ».

Le contrôleur de façade sert désormais seulement à tout attacher ensemble :


get('framework')->handle($request);

$response->send();

Si vous voulez une alternative plus légère pour votre container, envisagez l’utilisation de Pimple, un container d’injection de dépendances simple en environ 60 lignes de code.

Maintenant, voici comment vous pouvez enregister un écouteur personnalisé dans le contrôleur de façade :


$sc->register(
'listener.string_response',
'Simplex\StringResponseListener'
);
$sc->getDefinition('dispatcher')
->addMethodCall('addSubscriber', array(new Reference('listener.string_response')))
;

En plus de décrire vos objets, le container d’injections de dépendances peut également être configuré à l’aide de paramètres. Créons en un qui définit si nous sommes ou non en mode debug :


$sc->setParameter('debug', true);

echo $sc->getParameter('debug');

Ces paramètres peuvent être utilisés lorsque l’on décrit des définitions d’objets. Rendons le charset configurable :


$sc->register(
'listener.response',
'Symfony\Component\HttpKernel\EventListener\ResponseListener'
)->setArguments(array('%charset%'));

Après ce changement, il faut définir le charset avant d’utiliser l’objet d’écouteur de réponse :


$sc->setParameter('charset', 'UTF-8');

Au lieu de dire que les routes sont définies par la variable $routes, utilisons encore une fois un paramètre :


$sc->register(
'matcher',
'Symfony\Component\Routing\Matcher\UrlMatcher'
)->setArguments(array('%routes%', new Reference('context')));

Les changements correspondants dans le contrôleur de façade :


$sc->setParameter('routes', include __DIR__.'/../src/app.php');

Nous avons évidemment à peine effleuré la surface de ce que l’on peut faire avec le container : des noms de classe comme paramètres au remplacement de définitions d’objets déjà existants, du support de la portée à la génération d’une classe PHP à partir du container, et bien plus. Le container d’injection de dépendances de Symfony2 est vraiment puissant et est capable de gérer n’importe quelle classe PHP.

Ne me criez pas dessus si vous ne voulez pas de conainer d’injection de dépendances dans votre framework. Si vous ne l’aimez pas, ne l’utilisez pas. C’est votre framework, pas le mien.

C’est (déjà) la fin de ma série sur la création d’un framework avec les composants Symfony2. Je suis conscient que beaucoup de sujets n’ont pas été abordés en détails, mais j’espère que cela vous donnera assez d’informations pour commencer cotre framework et pour mieux comprendre comment les composants Symfony2 fonctionnent en interne.

Si vous voulez en apprendre plus, je vous recommande fortement de lire le code source du micro-framework Silex, et plus particulièrement de la classe Application.

Amusez vous bien !

~~ FIN ~~

P.S. : S’il y a suffisament d’intérêt (contactez l’auteur original dans les commentaires de cette page), je pourrais écrire quelques articles supplémentaires sur des sujets particuliers (l’utilisation d’un fichier de configuration pour le routage, utiliser les outils de debug de HttpKernel, l’utilisation du client embarqué pour simuler un navigateur sont certains des sujets qui me viennent à l’esprit par exemple).

Répondre

Unable to load the Are You a Human PlayThru™. Please contact the site owner to report the problem.