Raccourcis : Contenu - rubriques - sous rubriques
EN FR

Jelix propose un système de communication inter-module, basé sur le principe d'événements/écouteurs.

Il est possible d'émettre un événement de n'importe quel endroit dans votre code, et les modules qui "écoutent" cet événement pourront y répondre, et même renvoyer des données. C'est donc en quelque sorte un système de broadcast, avec des clients (les émetteurs) et des serveurs (les listeners).

Il y a donc plusieurs sortes d'objets :

  • un objet "évènement", qui contient le message et ses paramètres à émettre
  • des objets "écouteurs" (listeners) qui recevront et traiteront le message
  • un "distributeur", qui passe l'objet "évènement" à tous les listeners
  • un émetteur, l'objet qui donne un évènement au distributeur et qui recevra et traitera les réponses.

Depuis Jelix 1.8, il y a deux manières de créer un évènement :

  • soit en indiquant simplement son nom et d'éventuels paramètres
  • soit en instanciant un objet et le donner au distributeur

Émettre un évènement avec son nom

C'est la manière "historique" d'émettre un évènement.

L'objet jEvent sert à la fois d'émetteur d'événement, et en même temps de conteneur des réponses.

Pour émettre un événement, il faut utiliser la méthode statique notify().

Elle accepte en paramètre un nom d'évènement (qui n'est constitué que de caractères alphanumériques), et un tableau facultatif de paramètres (à utiliser selon l'évènement).

Vous recevez en retour l'objet jEvent instancié pour l'occasion, et contenant les réponses (ne pas confondre avec les objets jResponse, utilisés dans les contrôleurs). Les réponses sont un ensemble de valeurs dont la structure et le nombre dépend de l'événement et du nombre de modules ayant répondu.


   //exemple avec paramètre
   $eventParams = array('user' => $userObject);
   $theEvent = jEvent::notify('authCanLogin', $eventParams);
 
   //exemple sans paramètres
   $theEvent = jEvent::notify('authCanLogin2');

Pour accéder aux données des réponses retournés par les listeners des modules, il faut utiliser la méthode getResponse de l'objet jEvent qui a été retourné par notify:


  $reponses = $theEvent->getResponse();

Le résultat est un tableau des données que chaque module à renvoyé.

Vous avez d'autres méthodes pour manipuler les données retournées :

  • inResponse($responseKey, $value) : indique si il existe une clé avec la valeur donnée dans les données retournées.
  • getResponseByKey($responseKey) : renvoi la liste des données qui correspondent à la clé donnée (renvoi null si il n'y a aucune donnée avec cette clé)
  • allResponsesByKeyAreTrue($responseKey) : indique si toutes les données avec la clé donnée ont la valeur true.
  • allResponsesByKeyAreFalse($responseKey) : indique si toutes les données avec la clé donnée ont la valeur false.

Emettre un évènement avec un objet

Depuis Jelix 1.8, il est possible d'émettre des évènements sous forme d'objet que l'on instancie soit-même, et on le passe au distributeur.

L'avantage par rapport à la méthode classique des évènements nommés, est que l'on peut utiliser n'importe quel objet, avec des méthodes ou propriétés que les listeners peuvent appeler pour indiquer leur réponse, de manière plus spécifique que les méthodes getParam() ou add() de jEvent. On peut aussi continuer à utiliser un objet jEvent comme objet évènement.

L'objet représentant l'évènement doit implémenter l'interface Jelix\Event\EventInterface qui propose la méthode getName(). Cela permet d'indiquer un nom d'évènement particulier.

L'objet peut aussi implémenter l'interface Psr\EventDispatcher\StoppableEventInterface permettant à un listener de stopper la propagation de l'évènement aux listeners suivants.

Une fois l'objet évènement instancié, on le passe au distributeur. Celui-ci est donnée par la méthode Jelix\Core\Services::eventDispatcher(). Il implémente l'interface Psr\EventDispatcher\EventDispatcherInterface de la spécification PSR-14.

Exemple :



class \MyProject\MyEvent implements \Jelix\Event\EventInterface {
    protected $isOk = false;

    public function getName() {
        return 'thisIsMyEvent';
    }

    public function itsOk()
    {
        $this->isOk = true;
    }

    public function isItOk()
    {
        return $this->isOk;
    }
}

// creation de l'évènement
$event = new \MyProject\MyEvent();

// envoi de l'évènement aux listeners, qui peuvent par exemple appeler la
// méthode itsOk()
Jelix\Core\Services::eventDispatcher()->dispatch($event);

// on regarde ce qu'on répondu les listeners
if ($event->isItOk()) {
    //...
}

Répondre à un évènement

Il n'y a pas de listener par défaut dans les modules. Aussi, pour qu'un module puisse répondre à un événement, il faut créer un listener, et le déclarer dans le fichier events.xml du module. Il n'y a pas de limite du nombre de listener.

Il y a deux choses à faire pour un listener : le créer, puis le déclarer.

Créer le listener

Un listener est une classe stockée dans classes avec un nom spécifique.

Depuis Jelix 1.8.1, cela peut être une classe stockée n'importe où, avec n'importe quel nom, et qui doit être auto-chargeable. Ce qui est beaucoup moins contraignant.

Pour créer un listener dans classes, il faut d'abord lui donner un nom logique. Nous allons utiliser "auth" par exemple. Ensuite il faut créer une classe pour le listener. Son nom est une concatenation du nom logique avec "Listener". Stocker cette classe dans un fichier qui a pour nom nomlogique.listener.php, et donc auth.listener.php pour notre exemple, ce fichier devant être dans le dossier classes du module.

Une classe de listener doit hériter de \Jelix\Event\EventListener (ou de jEventListener mais cette classe est dépréciée) et doit contenir une méthode pour chaque évènement auquel le listener répond. Ces noms de méthodes commencent par on suivi du nom d'événement. Et ces méthodes prennent en paramètre l'objet jEvent correspondant à l'événement. Exemple :


class authListener extends jEventListener {
// ou
// class \MyModule\Listeners\Auth extends \jEventListener {

   function onAuthCanLogin ($event) {

        // recupération d'un paramètre de l'événment
        $user = $event->getParam('user');

        // traitement
        $ok = true;
        if (isset($user->actif)) {
            $ok = ($user->actif == '1');
        }

        $ok = $ok && ($user->password != '');

        // renvoi de donnée en réponse
        $event->add(array('canlogin'=>$ok));
   }
}

Ce listener répond à l'évènement "AuthCanLogin". La méthode récupère le paramètre 'user' de l'évènement. Et ajoute une donnée dans la réponse avec la méthode add. Ce paramètre et cette donnée de réponse dépendent uniquement de l'évènement AuthCanLogin. Pour d'autres évènements, il peut y avoir plusieurs paramètres ou aucun, d'autres types de données de réponses ou aucune réponse.

Pour les évènements dont le nom ne peut correspondre à un nom de méthode, comme un nom comportant des points (ex: foo.bar), vous pouvez indiquer quelle méthode il faut exécuter, dans une propriété eventMapping. Les clés sont les noms d'évènements, les valeurs les noms des méthodes.


class authListener extends jEventListener {

    protected $eventMapping = array(
      'foo.bar' => 'execFooBar'
    );

    function execFooBar (\jEvent $event) {
        // here process the event.
    }
}

Note : vous pouvez changer la façon dont votre listener traite les évènements (nom des méthodes correspondantes aux évènements etc), en redéfinissant la méthode performEvent() du listener.

Déclarer le listener

Il faut ensuite déclarer le listener. Cela se fait dans un fichier events.xml placé à la racine du module. Voici un exemple :


<events xmlns="http://jelix.org/ns/events/1.0">
   <listener name="auth">
       <event name="AuthCanLogin" />
   </listener>
   <!-- or -->
   <listener name="\MyModule\Listeners\Auth">
       <event name="AuthCanLogin" />
       <event name="thisIsMyEvent" />
   </listener>
</events>

L'attribute name de la balise listener doit contenir le nom logique du listener, si sa classe est stockée dans classes/, ou le nom entier de la classe si elle est auto-chargeable.

Vous pouvez déclarer autant de listener que vous voulez grâce à la balise <listener>. Et dans chacune d'elles vous indiquez tous les évènements que prend en charge le listener, grâce à des balises <event>.

Désactiver des listeners

Il peut arriver que vous vouliez désactiver un listener fourni par un module tiers, pour un tas de raison.

Cela est possible en les indiquant dans la section [disabledListeners] de la configuration principale. Les clés sont les noms d'évènements, les valeurs sont les sélecteurs des listeners ou le nom complet de la classe du sélecteur si elle est autochargeable.


[disabledListeners]
authLogin="aModule~theListener"

Dans cet example, le listener theListener du module "aModule" ne sera pas appelé quand l'évènement authLogin sera émis.

Si vous voulez désactiver plusieurs listener pour un même évènement, utilisez simplement la notation [] pour indiquer un tableau :


[disabledListeners]
authLogin[]="aModule~theListener"
authLogin[]="\MyModule\Listeners\Auth"