wiki:fr/drafts/ioc
developer.jelix.org is not used any more and exists only for history. Post new tickets on the Github account.
developer.jelix.org n'est plus utilisée, et existe uniquement pour son historique. Postez les nouveaux tickets sur le compte github.

Version 3 (modified by doubleface, 12 years ago) (diff)

--

Inversion de contrôle dans Jelix

Ce document vise à expliquer l'implémentation actuelle de l'inversion de contrôle dans Jelix, dans le but permettre aux contributeurs de l'améliorer plus facilement.

Introduction

Cette implémentation (très partielle) de l'inversion de contrôle est très inspirée de celle de Stubbles qui est très intéressante à regarder en introduction à ce document. J'ai tenté d'adapter cette implémentation à Jelix mais en supprimant beaucoup de choses comme l'utilisation des annotations ou de la réflexion afin de garder de bonnes performances et aussi en mettant au centre de cette implémentation les sélecteurs de Jelix. De plus, il n'y a pour le moment pas de gestion des paramètres du constructeur.

Utilisation

jClassBinding permet, lorqu'on fait appel à une interface par l'intermédiaire de jClasses, de laisser la possibilité, par configuration, de choisir l'implémentation qui se trouve derrière cette interface et donc d'en récupérer une instance. Exemple :

$instance = jClasses::getBindedService('module~serviceItf');

Si je n'ai rien fait de spécial avant et que j'exécute ce code, je vais récupérer une exception car j'ai essayé d'instancier serviceItf qui est une interface. Donc, comment associer serviceItf à un classe bien réelle : par un autre sélecteur vers cette classe. Voici les façons, par ordre de priorité pour faire cette association :

  • par un code spécifique :
    jClasses::bind('module~serviceItf')->to('module~serviceClass');
    
  • par le fichier de configuration de jelix:
    [classbindings]
    module-serviceItf = "module~serviceClass"
    

Noter que a gauche du "=", "~" est remplacé par un "-" car les "~" sont interdits dans les fichiers ini pour les clés.

  • par une constante spéciale dans l'interface :
    interface serviceItf {
      const JBINDING_BINDED_IMPLEMENTATION = "module~serviceClass";
    }
    

Si on utilise jClasses::getBindedService, l'instance associée est un sungleton alors que si on utilise jClasses::createBinded, on à chaque fois une nouvelle instance.

Il est aussi possible de lier une classe à une autre classe :

jClasses::bind('class:module~serviceClass')->to('module~serviceClassBis');

Si on essaye d'instancier la liaison d'une classe et que celle-ci n'en a pas, sa propre implémentation sera utilisée :

jClasses::getBindedService('module~classWithoutBinding'); // retourne une instance de classWithoutBinding

Il est aussi possible de lier directement une classe ou une interface à une instance. C'est très pratique en particulier encore pour les test unitaire en utilisant ceci conjointement avec les Mock Objects :

// Supposons que l'on soit dans un test PHPUnit
$CalledMock = $this->getMock('Called');                        // On crée un mock object
jClasses::bind('module~CalledItf')->toInstance($CalledMock);
Caller->methodToTest();

Choix

En fait, cette implémentation est un conteneur de dépendances. Les autres implémentations on plutôt fait le choix de laisser à l'utilisateur de créer eux même leur conteneur de dépendances. Si on en ressent le besoin, peut-être devra-t-on aussi laisser cette possibilité et proposer un conteneur par défaut afin de garder la pour pouvoir garder l'interface actuelle ?

Manques

  • Pas de gestion des paramètre de constructeur
  • Sûrement d'autres chose (à compléter)