Aller au menu - Aller au contenu

Icône Les Events Listeners

Avatar
Mise à jour : 04/09/2010
Difficulté : Intermédiaire Intermédiaire Creative Commons BY-NC-SA
487 visites depuis 7 jours, dont 10 sur ce chapitre classé 233/786
Dans cette partie, nous nous intéresserons aux Events Listeners. Comme leur nom l'indique, ces choses bizarres permettent... d'écouter des évènements !
Non, je ne vous prends pas pour des billes, nous allons voir immédiatement de quoi il s'agit.

De manière générale et théorique, lorsqu'un évènement est déclenché, les listeners associés en sont informés, et peuvent agir en conséquence. Ils peuvent non seulement faire leur petit travail de leur côté, mais aussi, et c'est très important, modifier l'exécution de l'évènement.

Petite précision avant de commencer : cette partie restera assez théorique, mais nous (c'est-à-dire vous :-° ) mettrons tout cela en pratique dans un prochain TP.
Sommaire du chapitre :
Icône du chapitre
Chapitre précédent Sommaire Chapitre suivant

Introduction aux Events Listeners

Déclenchement d'évènements



La première chose à connaître, lorsque l'on parle d'évènements, est de savoir quand ils sont déclenchés.
Dans Doctrine, nous ne nous occuperons pas de les déclencher, cela est fait automatiquement. Par exemple, lors d'un enregistrement dans la base de données, ou lors d'une sélection de données, etc.

Pour ceux qui utilisent Symfony ou qui ont l'habitude de déclencher des évènements manuellement, gardez bien à l'esprit qu'avec Doctrine, nous ne parlons que d'Event Listener, ce qui signifie que nous ne faisons que les écouter. Le framework Symfony (entre autres) intègre une gestion complète des évènements, ce qui vous permet d'en déclencher vous-même. Ce n'est pas le cas ici !


Les différents composants de Doctrine déclenchent à plusieurs moments ces évènements. Nous allons les étudier juste après.

Intercepter un évènement



C'est le rôle des listeners et des hooks.

  • Un hook est une méthode vide intégrée à l'objet écouté.
  • Un listener est un objet à part, qui est lié à l'objet écouté. Il implémente lui-même les hooks, ce qui permet une meilleure séparation et organisation des différentes facettes de l'application.


Vous comprendrez mieux tout cela par la suite, ne vous inquiétez pas. ;)

Les classes susceptibles de déclencher des évènements, sont les suivantes :

  • Doctrine_Record ;
  • Doctrine_Validator ;
  • Doctrine_Connection ;
  • Doctrine_Transaction ;
  • Doctrine_Connection_Statement.


Les méthodes utilisées



Les méthodes (que ce soient des hooks, ou que ce soient celles des listeners) que nous allons utiliser seront expliquées par la suite. Leur utilisation est intuitive, voyons un exemple :

Code : PHP
1
2
3
4
5
6
7
8
<?php
class Article extends Doctrine_Record
{
    public function preSave($event)
    {
        $this->user_id = $_SESSION['user_id'];
    }
}


Dans cet exemple, nous utilisons la méthode preSave() (un hook). Comme son nom l'indique, cette méthode est appelée juste avant l'enregistrement de l'article dans la base de données. Nous en profitons pour indiquer que c'est l'utilisateur courant qui a modifié cet article en dernier. Comme nous sommes à l'intérieur de l'objet en question, nous avons bien entendu accès à la variable $this, et nous pouvons le modifier directement, juste avant son enregistrement.

Les méthodes utilisées respectent toutes la même logique : elles sont préfixées par pre ou post, selon qu'elles soient appelées avant ou après l'évènement (ici, save).
Pour finir, elles reçoivent en paramètre un objet $event, instance de Doctrine_Event.

Mais comment Doctrine peut-il savoir que j'ai écrit la méthode preSave() si je ne le lui dis pas ?

En fait, tous les hooks sont déjà définis dans la classe Doctrine_Record : allez y jeter un coup d'œil. Ce sont des méthodes vides, et Doctrine les appelle à chaque évènement. Seulement, tant que vous ne redéfinissez pas cette méthode dans une classe fille (merci l'héritage :-° ), et bien il ne se passe rien !
C'est la même chose à chaque fois : l'astuce est simplement d'utiliser des méthodes vides !

Doctrine_Event



Les objets Doctrine_Event permettent d'agir sur certains objets. Par exemple, si l'évènement est l'exécution d'une requête, on peut récupérer celle-ci, et ainsi la modifier.

Voici quelques méthodes utilisées fréquemment :

getInvoker()



Retourne l'objet qui a « invoqué » l'évènement. Par exemple, un objet Doctrine_Record.
Il y a toujours un Invoker, et il s'agit d'une instance de l'une des classes citées plus haut.

Nous pouvons donc reformuler l'exemple précédent :

Code : PHP
1
2
3
4
5
6
7
8
<?php
class Article extends Doctrine_Record
{
    public function preSave($event)
    {
        $event->getInvoker()->user_id = $_SESSION['user_id'];
    }
}


getQuery()



Retourne l'objet Doctrine_Query associé à l'évènement, s'il existe.
Attention, il peut ne pas exister ! Dans l'exemple précédent, cela renverra la valeur null.

getParams()



Retourne un tableau d'éventuels paramètres. Par exemple, le paramètre alias contient l'alias utilisé dans la requête.

Voici les méthodes les plus courantes. Je vous invite à ouvrir le fichier Event.php pour plus de détails. Si vous avez bien suivi le début du tuto, vous devriez le trouver dans ~/lib/vendor/doctrine/Doctrine/. ;)

Listener de connexion

Dans ce chapitre, nous nous intéressons aux listeners de connexion, c'est-à-dire les listeners que l'on peut attacher à la classe Doctrine_Connection.
Je vous l'ai dit dans le chapitre précédent, un listener est une classe qui implémente des méthodes spécifiques.

Créer un listener



Il existe trois manières de créer un listener.

Étendre une classe de base



Nous appellerons notre listener fictif CustomListener. Doctrine_EventListener offrant une base pour tout listener, le nôtre en héritera donc :

Code : PHP
1
2
3
4
<?php

class CustomListener extends Doctrine_EventListener
{ }


Ouvrez le fichier ~/lib/vendor/doctrine/Doctrine/EventListener.php. Vous devriez maintenant mieux comprendre l'astuce des méthodes vides que je vous avais expliquée. La seule chose qu'il vous reste à faire maintenant, c'est de surcharger les méthodes que vous voulez implémenter. Chaque méthode porte un nom assez explicite, et je pense que vous devriez vous y retrouver. ;)

Étendre une autre classe



Si, pour une raison quelconque, votre listener ne peut pas hériter de Doctrine_EventListener (parce qu'il hérite déjà d'une autre classe, par exemple), il va falloir écrire en dur chaque méthode.

Explications
La seule condition pour créer un listener, c'est en fait qu'il implémente l'interface Doctrine_EventListener_Interface. Comme vous avez pu le voir en ouvrant le fichier EventListener.php, la seule chose que fait la classe Doctrine_EventListener est d'implémenter cette interface. Elle définit toutes les méthodes. Ainsi, la classe fille (CustomListener) implémente indirectement cette même interface, tout en n'étant pas obligée de redéfinir toutes les méthodes.
Donc, si vous n'utilisez pas toutes les méthodes dans votre listener, vous devrez quand même les définir !

Un exemple de code sera certainement plus parlant :

Code : PHP
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<?php

class CustomListener2 extends UneAutreClasse implements Doctrine_EventListener_Interface
{
    public function preTransactionCommit(Doctrine_Event $event)
    { }
    
    public function postTransactionCommit(Doctrine_Event $event)
    { }
    
    // Et ainsi de suite avec toutes les méthodes imposées par l'interface...
}


Bien sûr, c'est ici que vous personnalisez chaque méthode comme vous le voulez.

Le « faux » listener...



La dernière manière de créer un listener, est d'implémenter l'interface Doctrine_Overloadable. Cette interface n'impose qu'une chose : l'implémentation de la méthode magique __call().
Cette méthode sera appelée lors de chaque évènement, à vous ensuite de traiter tout ça comme vous le voulez. ;)

Pour rappel, la méthode __call() reçoit deux paramètres : le nom de la méthode appelée, et un tableau des arguments.


Code : PHP
1
2
3
4
5
6
7
8
<?php
class CustomListener3 implements Doctrine_Overloadable
{
    public function __call($method, $arguments)
    {
        $event = $arguments[0];
    }
}


Attacher un listener



La dernière chose à savoir, c'est comment lier le listener à l'objet écouté.
Ceci se fait d'une manière très simple : Doctrine_Connection possède la méthode addListener() qui prend en paramètre... un listener !

Un exemple est plus simple que de longues explications :

Code : PHP
1
2
3
4
5
6
7
<?php

$listener1 = new CustomListener();
$listener2 = new CustomListener2();

$connexion->addListener($listener1);
$connexion->addListener($listener2);

Comme vous le voyez, il est possible d'ajouter plusieurs listeners. Lors d'un évènement, ils seront appelés dans le même ordre que celui dans lequel ils ont été affectés.

Notez que s'il n'y a qu'un seul listener, vous pouvez utiliser la méthode setListener(). Cela supprime également d'éventuels listeners existants.


Les listeners attachés à Doctrine_Connection écoutent également les classes proches, comme Doctrine_Transaction et Doctrine_Connection_Statement. Néanmoins, le listener est global à ces trois classes, et doit être attaché à Doctrine_Connection.

Je vous invite à vous référer à la documentation officielle (in english of course :D ) pour avoir le détail de chaque méthode.
Dans les tableaux, la colonne de gauche indique le nom de la méthode du listener, celle du milieu indique quelle méthode est écoutée, et la troisième contient d'éventuels paramètres accessibles de l'objet Doctrine_Event.

Par exemple, lors de l'appel de Doctrine_Connection::connect(), avant toute action, la méthode preConnect() du/des listener(s) est appelée. Une fois que toutes les actions sont effectuées, c'est au tour de la méthode postConnect().

Je vous rappelle aussi que toutes les méthodes reçoivent un objet Doctrine_Event en paramètre.

Je ne m'attarde pas plus sur les listeners de connexion, ça ne vous sera pas forcément utile pour une utilisation basique.

Listeners d'objets

Nous allons voir ici des listeners que vous trouverez certainement plus utiles. Ceux-ci sont appelés par les classes Doctrine_Record et Doctrine_Validator.

Créer un listener



De la même manière que pour les connections, il y a trois façons de créer un listener :
  • hériter de la classe Doctrine_Record_Listener ;
  • implémenter l'interface Doctrine_Record_Listener_Interface ;
  • implémenter l'interface Doctrine_Overloadable.


La manière de créer cette classe est identique à Doctrine_Connection, référez-vous donc au chapitre précédent. ;)

Voici le lien vers la doc pour plus de détails et la liste des méthodes : www.doctrine-project.org.

Attacher un listener



Notre listener peut être attaché au niveau global, au niveau d'une connexion ou au niveau d'une table.

Globalement



Il faut l'attacher à Doctrine_Manager. Toutes les tables de toutes les connexions en bénéficieront alors.

Code : PHP
1
2
3
4
5
6
<?php

class CustomRecordListener extends Doctrine_Record_Listener
{ /* ... */ }

Doctrine_Manager::getInstance()->addRecordListener(new CustomRecordListener());


Par connexion



En attachant le listener à une instance de Doctrine_Connexion, toutes les tables de cette connexion sont affectées.
Néanmoins, je pense que la plupart du temps, vous n'avez qu'une connexion à une seule base de données dans votre projet. Dans ce cas, cela équivaut à l'attacher globalement.

Code : PHP
1
2
<?php
$connexion->addRecordListener(new CustomRecordListener());


Par table



C'est certainement la fonctionnalité la plus intéressante, ou du moins celle dont vous vous servirez le plus. Il est possible d'attacher un listener à une table particulière.

Code : PHP
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<?php

Doctrine_Core::getTable('Article')
    ->addRecordListener(new CustomRecordListener());

// Équivalent à :

class Article extends Doctrine_Record
{
    public function setUp()
    {
        $this->addListener(new CustomRecordListener());
    }
}


Ceci nous sera extrêmement utile, notamment, avec l'utilisation de templates...

Les hooks



Je vous en avais parlé en introduction, les hooks sont des listeners « simplifiés ». Ce sont de simples méthodes implémentées directement dans l'objet.

Code : PHP
1
2
3
4
5
6
7
8
9
<?php

class Article extends Doctrine_Record
{
    public function preSave($event)
    {
        // On fait ce que l'on veut juste avant l'enregistrement de l'objet.
    }
}


Comme vous le voyez, les hooks sont plus simples à utiliser que les listeners. Cependant, lorsque vous devez en utiliser beaucoup, préférez les listeners, cela permet une meilleure organisation du code.

Intercepter les requêtes



Il existe deux méthodes pour intercepter les requêtes : avec des hooks et dans les listeners.

Elles sont cependant identiques, et consistent en ces trois méthodes :

  • preDqlSelect() est appelée lors d'une requête de type SELECT ;
  • preDqlUpdate() est appelée lors d'une requête de type UPDATE ;
  • preDqlDelete() est appelée lors d'une requête de type DELETE.


Leur fonctionnement est identique aux autres méthodes vues précédemment. Notez cependant que Doctrine_Event::getQuery() retourne dans ce cas la requête elle-même, ce qui offre la possibilité de la modifier.

Code : PHP
1
2
3
4
5
6
7
8
9
<?php

class CustomRecordListener extends Doctrine_Record_Listener
{
    public function preDqlSelect(Doctrine_Event $event)
    {
        $event->getQuery()->addWhere('ce que vous voulez...');
    }
}


Vous pouvez ainsi filtrer des données lors d'un SELECT, par exemple.

Attention !
Par défaut, ces trois méthodes ne sont pas appelées ! Pour les utiliser, il faut le spécifier explicitement, comme ceci :
<?php $manager->setAttribute(Doctrine_Core::ATTR_USE_DQL_CALLBACKS, true).
En effet, ceci ajoute une surcharge de travail, puisque Doctrine doit, pour chaque requête, appeler ces callbacks pour chaque table incluse dans la clause FROM.
Rendez-vous dans la partie Annexes pour plus de détails sur les attributs de Doctrine_Manager.

Informations supplémentaires

Interrompre l'action en cours



Il est possible d'annuler totalement l'évènement, grâce à la méthode Doctrine_Event::skipOperation().

Code : PHP
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<?php

class CustomRecordListener extends Doctrine_Record_Listener
{
    public function preSave(Doctrine_Event $event)
    {
        // Vous faites ce que vous voulez ici...

        $event->skipOperation();
    }
}

class Article extends Doctrine_Record
{
    public function setUp()
    {
        $this->addListener(new CustomRecordListener());
    }
}

Attention, avec cet exemple, vos objets ne pourront pas être enregistrés avec la méthode save()... :D


Ne pas exécuter le listener suivant



Lorsque vous ajoutez plusieurs listeners à une classe, il peut être utile, lors de l'appel de l'un d'entre eux, que le suivant (celui qui a été attaché juste après) ne soit pas appelé.
Ceci est permis par Doctrine_Event::skipNextListener().

Code : PHP
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php

class CustomRecordListener1 extends Doctrine_Record_Listener
{
    public function preSave(Doctrine_Event $event)
    {
        /* ... */

        $event->skipNextListener();
    }
}

class CustomRecordListener2 extends Doctrine_Record_Listener
{
    public function preSave(Doctrine_Event $event)
    { /* ... */ }
}

$record->addListener(new CustomRecordListener1());
$record->addListener(new CustomRecordListener2());

$record->save();


En temps normal, les méthodes preSave() de chaque listener sont appelées dans l'ordre (CustomRecordListener1::preSave() puis CustomRecordListener2::preSave()). Cependant ici, CustomRecordListener1::preSave() demande de « sauter » le prochain listener. CustomRecordListener2::preSave() ne sera donc jamais exécutée.
Ce petit chapitre est maintenant terminé et il vous a permis de découvrir les Events Listeners de Doctrine. Bien que ce chapitre soit très théorique, dans le prochain nous étudierons les templates. Ce qui permettra de mettre en pratique avec un TP tout ce que vous avez appris : vous ajouterez un listener à un template... ;)
Chapitre précédent Sommaire Chapitre suivant

Partager

1 commentaire pour "Les Events Listeners"
Note moyenne : 3.54 / 4 (35 votes)
Pseudo Commentaire
Hors ligne Nami Doc # Posté le 30/08/2010 à 15:35:02
Lamaer taler dansk

Avis : Très bon

Typo: il manque un "à": "et doit être attaché Doctrine_Connection."

La flemme conquerra le monde !
Secret (cliquez pour afficher)
Image utilisateur
 

Voir tous les commentaires
Ce tutoriel a été corrigé par les zCorrecteurs.