Le but de cet article n’est pas d’expliquer le fonctionnement des événements symfony, la documentation officielle est très bien faite à ce sujet, et pas mal d’articles expliquant leur implementation existent déjà.

Le but est juste de savoir comment faire pour avoir accès à l’event dispatcher depuis les classes du modèle, de manière à pouvoir lever des événements « métiers » qui permettent des traitements qui se rapprochent des trigger sql. L’avantage est de rester au sein de l’application symfony et ne pas disperser la logique. C’est une problèmatique que nous avons rencontré à plusieurs reprises dans les projets et nous avons pu trouver cette implementation grace à n1k0 de chez Akei.

(Pour la suite, je vais me servir d’une partie du modèle de Jobeet comme je l’avais fait dans mon post précédent)

A la base, l’event dispatcher se trouve dans le contexte, et donc accessible principalement depuis les actions des modules. L’idée est de le rendre accessible aux classes du modèle doctrine de manière à pouvoir notifier des événements « métier » directement depuis les méthodes. On va donc passer l’event dispatcher à la classe de base dont hérite toutes les classes du modèle. Ou plus exactement, on va rajouter une classe custom dans la pile d’héritage des classes du modèle.

On créé donc une classe CustomDoctrineRecord, que je place dans /lib/model/doctrine/record/CustomDoctrineRecord.class.php pour éviter qu’elle se retrouve noyée avec les classes du modèle. On va faire passer à cette classe l’event dispatcher par des méthodes statiques.
Les classes de base du modèle héritent de sfDoctrineRecord, il faut donc impérativement que la classe custom hérite elle aussi de sfDoctrineRecord.

Fichier : /lib/model/doctrine/record/CustomDoctrineRecord.class.php


Maintenant il faut spécifier à doctrine d'utiliser notre classe custom comme classe de référence des classes modèles, on utilise la méthode configureDoctrine() de sfProjectConfiguration. Aprés quoi on va avoir besoin de passer l'event dispatcher à notre classe. On va donc catcher l'événement "context.load_factories" qui est déclenché une fois que le contexte est initialisé pour aller initialiser la classe CustomDoctrineRecord.

Fichier : /config/ProjectConfiguration.class.php

enablePlugins(array(
      'sfDoctrinePlugin',
    ));
    
    $this->dispatcher->connect('context.load_factories', array($this, 'listenToContextFactoriesLoaded'));
  }

  /**
   * Defines the base objects' classe
   */
  public function configureDoctrine(Doctrine_Manager $manager)
  {
    sfConfig::set('doctrine_model_builder_options', array(
      'baseClassName' => 'CustomDoctrineRecord'
    ));
  }

  /**
   * Objects can acces the EventDispatcher
   */
  public function listenToContextFactoriesLoaded(sfEvent $event)
  {
    CustomDoctrineRecord::setEventDispatcher($event->getSubject()->getEventDispatcher());
  }
}

Aprés ca, il nous reste à faire un petit

./symfony doctrine:build --all-classes

Et on peut vérifier l'inpact en ouvrant une classe de base, et on trouve bien que la classe hérite de notre classe CustomDoctrineRecord

Fichier : /lib/model/doctrine/base/BaseJobeetJob.class.php


Il est donc maintenant possible de lever des événements depuis les classes du modèle. Pour exemple je vais lever un événement à l'insertion d'un nouveau Job. Cet événement pourra être utilisé par la suite pour faire ce que l'on veut, comme par exemple envoyer une email au modèrateur l'informant qu'un nouveau job a été saisi sur le site.

Fichier : /lib/model/doctrine/JobeetJob.class.php

hasListeners('JobeetJob.Inserted'))
      {
        self::getEventDispatcher()->notify(new sfEvent($this, 'JobeetJob.Inserted'));
      }
    }
  }
}

C'est un exemple tout simple, mais les possibilités sont immenses. Attention aux événements en cascades, ca peut très vite devenir un casse tête à debugger !

Dernier petit tips, pour pouvoir faire des tests de vos méthodes qui lèvent des événements : c'est à mis chemin entre les tests unitaires et les tests fonctionnels. J'ai voulu tester des enchainements de méthodes qui levaient des événements, et je voulais vérifier que les règles de gestion étaient respectées. Je suis donc parti du bootstap unitaire où j'ai initialisé un contexte que je passe à ma classe de base, de manière à ce que les événements soient diffusés.

Fichier : /test/bootstrap/unit_event.php

getEventDispatcher());

Quand on commence à utiliser les événements, il y a deux réactions possibles. Il y a les réfractaires qui disent "c'est null, ca sert a rien", soit on devient accro et on commence à en mettre de partout. Si vous faites parti du deuxième groupe, voici quelques conseils élémentaires mais important à respecter pour éviter que le debuggage se transforme en calvaire :

  • mettre des noms d'événements explicites ;
  • centraliser les écouteurs.

Bon codage à tous, et bonnes fêtes 😉

Previous ArticleNext Article

This post has 4 Comments

4
  1. Je suis retourné voir la doc vu qu’il me semblait qu’il y avait un moyen simple d’accéder à l’event dispatcher depuis absolument partout, et ce qu’ils disent c’est:

    $dispatcher = ProjectConfiguration::getActive()->getEventDispatcher();
    permet de récupérer l’event dispatcher depuis n’importe où dans l’application.

    Moi j’ai commencé à utiliser les évènements récemment, ma réaction serait plutôt, « mais pourquoi on n’a pas ça direct en standard dans le langage? ». Plutôt côté accros donc!

  2. Avec ce système, vous mixez au sein du même projet le système d’événements de Doctrine et celui de Symfony.

    Je suis bien placé pour savoir que ça peut provoquer de mauvaises surprises. J’ai utilisé cette technique sur un projet qui lançait/écoutait un grand nombre d’événements (https://freelancer-app.fr) et je m’en suis mordu les doigts.

    Certains événements dépendaient du système de Doctrine et d’autres de celui de symfony. Je ne savais parfois plus qui lançait/écoutait quoi. Sur certains événements complexes, j’ai finis par me retrouver avec des problèmes liés à l’ordre d’appel des listeners et de synchronisation des objets en mémoire.

    J’ai presque fini à un refactoring en profondeur de mon projet, la quasi-totalité de mes événements sont gérés par doctrine.
    Je trouve que l’organisation du code s’en trouve bien mieux et qu’elle est au final plus logique: tous les événements qui modifient un composant du modèle sont gérés par les événements de Doctrine dont c’est quand même le boulot.

    Les quelques événements symfony restants sont très marginaux, comme par exemple des alertes emails qui ne modifient pas en retour le modèle.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *