En effet, il est parfois délicat d'injecter des objets dynamiquement, notamment lorsque leur création dépend d'informations internes à la classe qui les utilise, comme dans le cas suivant :

<?php

namespace mageekguy\atoum\phar;
class generator {
   ...  
   public function run()
   {
      ...
      $phar = new \phar($this->destinationDirectory . DIRECTORY_SEPARATOR . self::name);
      $phar->buildFromIterator(new \directoryIterator($this->originDirectory));
      ...
   }
   ... }
?>

Dans ce cas, la création de l'objet stocké par la variable $phar dépend de la concaténation de $this->destinationDirectory et de self::name, la création de l'itérateur permettant de remplir l'archive phar dépend de $this->originDirectory, et l'ensemble de ces informations est inaccessibles de l'extérieur de la classe.

De plus, il n'est pas possible de possible de modifier le fichier de destination d'un objet phar après sa création, et il en va de même dans le cas de l'itérateur.

Il serait bien possible de passer par des accesseurs, de la manière suivante :

<?php
...
$generator->run(new \phar($generator->getPharName(), new \directoryIterator($generator->getDestinationDirectory());
...
?>

Cependant, cette solution est de mon point de vue tirée par les cheveux, d'autant que dans le cas qui nous occupe, la récupération du répertoire de destination à partir des arguments passés en ligne de commande se fait dans la méthode run().

En conséquence, il semble à priori difficile de mettre en œuvre l'injection de dépendances dans ce cas, du moins avec la méthode classique.

Il faut donc se tourner vers d'autres solutions, comme Crafty, dont l'utilisation est décrite par l'ami Clochix, ou l'injecteur de dépendances de Symfony.

Cependant, ces solutions, indépendamment de leurs qualités respectives, sont relativement lourdes, ou tout du moins trop lourdes à mon goût.

J'ai donc choisi une autre voie en utilisant l'une des fonctionnalités de PHP 5.3 que j'affectionne particulièrement actuellement, à savoir les fermetures.

En effet, malgré leurs limitations, elles sont suffisamment puissantes et intéressante dans le cas présent.

Pour cela, il faut commencer par définir deux propriétés protégées au niveau de notre classe :

<?php

namespace mageekguy\atoum\phar;
class generator { 
   ...
   protected $pharInjector = null;
  protected $fileIteratorInjector = null;
   ... }
?>

Une fois cela fait, il nous faut modifier le constructeur de notre classe afin de définir lors de l'instanciation de notre classe les injecteurs de dépendances par défaut :

<?php

namespace mageekguy\atoum\phar;
class generator { 
   public function __construct(...)
   {
      $this->pharInjector = function($name) { return new \phar($name); };
      $this->fileIteratorInjector = function($directory) { return new \directoryIterator($directory); };
   }
   ... }
?>

Il faut ensuite définir deux méthodes qui permettront au développeur de définir les injecteurs de son choix si ceux proposés par défaut ne conviennent pas :

<?php

namespace mageekguy\atoum\phar;
class generator { 
   public function setPharInjector(\closure $pharInjector)
   {
      $this->pharInjector = $pharInjector;
      return $this;
   }

   public function setFileIteratorInjector(\closure $fileIteratorInjector)
   {
      $this->fileIteratorInjector = $fileIteratorInjector;
      return $this;
   }
   ... }
?>

Enfin, il faut remplacer les appels à l'opérateur new effectués sur les classes \phar et directoryIterator par une invocation de nos injecteurs :


<?php

namespace mageekguy\atoum\phar;
class generator {
   ...  
   public function run()
   {
      ...
      $phar = $this->pharInjector->__invoke($this->destinationDirectory . DIRECTORY_SEPARATOR . self::name);
      $phar->buildFromIterator($this->fileIteratorInjector->__invoke($this->originDirectory));
      ...
   }
   ... }
?>

Grâce à ce mécanisme, il est maintenant possible d'utiliser les classes de son choix dans la méthode run() pour construire notre archive phar.

Dans le cadre d'un test unitaire écrit avec Atoum, cela donne le code suivant :

<?php
namespace mageekguy\atoum\tests\units\phar;

use \mageekguy\atoum; use \mageekguy\atoum\mock; use \mageekguy\atoum\phar;
require_once(__DIR__ . '/../../runner.php');
/** @isolation on */ class generator extends atoum\test {    public function testRun()    {
     ...
      $mockGenerator = new mock\generator();      
      $pharController = new mock\controller();
     $pharController->injectInNextMockInstance();       $pharController->__construct = function() {};       $pharController->setStub = function() {};       $pharController->setMetadata = function() {};       $pharController->buildFromIterator = function() {};       $pharController->setSignatureAlgorithm = function() {};    
      $mockGenerator->generate('\phar');       $phar = new mock\phar(uniqid());
      $generator->setPharInjecter(function($name) use ($phar) { return $phar; });    
      $fileIteratorController = new mock\controller();       $fileIteratorController->injectInNextMockInstance();       $fileIteratorController->__construct = function() {};      
      $mockGenerator->generate('\recursiveDirectoryIterator');       $iterator = new mock\recursiveDirectoryIterator(uniqid());    
      $generator->setFileIteratorInjecter(function($directory) use ($iterator) { return $iterator; });      
      $this->assert          ->object($generator->run())->isIdenticalTo($generator)       ;
      ...
   } }
?>

Et oui, comme vous pouvez le constater dans le code ci-dessus, les fermetures peuvent servir à beaucoup de choses, puisque dans ce cas, elles permettent de définir le comportement des mocks.

Pour en revenir à l'injection de dépendances, il est possible de renforcer le système en vérifiant le nombre d'arguments passés aux injecteurs, en faisant par exemple appel à la reflection de PHP, et en contrôlant le type de variable renvoyé par l'invocation des injecteurs à l'aide de l'opérateur instanceof.

Et vous, vous injectez vos dépendances de quelle manière ?