Skip to content

Instantly share code, notes, and snippets.

@t-geindre
Last active November 12, 2015 16:36
Show Gist options
  • Save t-geindre/a01eb78e5b30da82b516 to your computer and use it in GitHub Desktop.
Save t-geindre/a01eb78e5b30da82b516 to your computer and use it in GitHub Desktop.
[SF2] Recommandations sur l'écritures des commandes

Votre commande n'est qu'un point d'entrée

Ceci semble évident pour tout le monde lorsque j'évoque un controller, mais beaucoup moins lorsqu'il s'agit d'une commande. Pourtant, ces deux éléments ne constituent que des points d'entrée dans votre application et ne devraient, en aucun cas, contenir de la logique métier.

La logique métier doit se trouver dans des services dont c'est l'unique rôle.

Tout l'intérêt est biensur de pouvoir réutiliser ces services, dans des contextes différents, sans qu'ils ne soient couplés au premier environnement dans lequel vous pensiez les utiliser.

Ne designez pas vos services pour vos commandes

Bien souvent, quand j'ai expliqué ce premier point à quelqu'un, il se met à développer un ou plusieurs services qui sont plus moins couplés avec les objets qui découlent de l'environnement d'exécution CLI.

Plus globalement, ces services sont adaptés pour tourner en CLI.

C'est une erreur. Écrivez vos services pour qu'ils répondent au besoin métier de votre application sans tenir compte du contexte dans lequel ils seront exécutés. Un service doit tout aussi bien pouvoir être appelé depuis une commande que depuis un controller.

Concrétement, vous ne drevez pas y trouver d'objet Response ou OutputInterface, jamais. Séparez les responsabilités, toujours.

Oui mais comment je log des choses dans ma commande ?

La grande question qui revient à chaque fois que j'aborde ce sujet. Finalement, comment est-ce qu'on fait pour communiquer avec notre service pour en tirer des informations et les afficher à l'utilisateur ?

Injecter, à notre service, une instance d'une InputInterface serait, comme on l'a vu, stupide. Notre service serait alors couplé à des objets relatifs à un environnement CLI qui n'auraient aucun sens dans un autre cadre (une requête HTTP par exemple).

Le patterne EventDispatcher est parfait pour résoudre ce problème. Un service, au cours de son exécution, peut lever des évènements qui peuvent être écoutés par n'importe quel autre code. Le service ne fait qu'alerter l'ensemble du système de ce qu'il effectue et d'autre composants peuvent choisir de réagir à ces évènements, ou pas.

Concrètement, voici ce que ça peut donner :

protected function execute(InputInterface $input, OutputInterface $output)
{
    $container  = $this->getContainer();
    $dispatcher = $container->get('event_dispatcher');

    $dispatcher->addListener(Event::MY_EVENT, function(MyEvent $event) use ($output) {
        $output->writeLn(sprintf('<info>%s</info>', $event->Name()));
    });

    $container
        ->get('my_vendor.my_service')
        ->executeSomeJob();
}

L'injection c'est le bien

L'exemple précédent présente encore quelques problèmes. Tout d'abord, c'est la commande qui décide à quel service elle va faire appel. Ceci ne devrait pas être de sa responsabilité. Elle devrait simplement s'assurer qu'elle reçoit un objet qui est en mesure remplir la fonction souhaité. On résoud bien souvent ce genre de problème par un contrat d'interface. Notez d'ailleurs que pour l'instant rien de permet d'assurer que le service utilisé respecte l'interface attendu.

Solution : tout injecter, command as service !

Symfony permet en effet de configurer des commandes comme n'importe quel autre service.

Command as a service

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment