2015-02-27 5 views
2

J'ai commencé à utiliser les composants de console de symfony pour créer divers outils cli.Charger une configuration personnalisée dans une commande de console en utilisant l'injection de dépendances

Je suis actuellement en train de gifler ensemble une telle application de console, qui a besoin de diverses configurations, dont certaines sont partagées entre les commandes, d'autres configs sont uniques à la commande. Dans un premier temps, j'utilisais une classe auxiliaire, avec un appel de fonction statique pour charger une matrice de configuration régulière.

Hier, j'ai refactorisé cela et maintenant charger la configuration dans le composant config, avec le mécanisme treeBuilder pour la validation. Tout est fait dans le script de la console principale, pas dans les classes "command".

$app = new Application('Console deployment Application', '0.0.1'); 


/** 
* Load configuration 
*/ 
$configDirectories = array(__DIR__.'/config'); 
$locator = new FileLocator($configDirectories); 

$loader = new YamlConfigLoader($locator); 

$configValues = $loader->load(file_get_contents($locator->locate("config.yml"))); 

// process configuration 
$processor = new Processor(); 

$configuration = new Configuration(); 

try { 
    $processedConfiguration = $processor->processConfiguration(
    $configuration, 
    $configValues 
); 

    // configuration validated 
    var_dump($processedConfiguration); 

} catch (Exception $e) { 
    // validation error 
    echo $e->getMessage() . PHP_EOL; 
} 

/** 
* Load commands 
*/ 
foreach(glob(__DIR__ . '/src/Command/*Command.php') as $FileName) { 
    $className = "Command\\" . rtrim(basename($FileName), ".php"); 
    $app->addCommands(array(
     new $className, 
    )); 
} 

$app->run(); 

Actuellement, le seul moyen de configurer la configuration consiste à configurer le code qui charge la configuration dans une classe séparée et appeler cette classe dans la méthode configure() de chaque méthode.

Peut-être qu'il y a une façon plus "symfonyish" de le faire que j'ai raté, je voudrais aussi éviter d'avoir tout le framwework dans le code, c'est une application de console légère.

Y at-il un moyen de passer la configuration traitée aux commandes invoquées, en utilisant DI ou une autre méthode que je ne connais pas?

+0

Avez-vous besoin de différentes configurations pour certaines/toutes les commandes? Avez-vous besoin d'autres dépendances en plus de la configuration dans certaines/toutes les commandes? Si vous avez répondu 2x non, alors pourquoi pas 'new $ className ($ configuration)'? –

+0

@FaKeller - Je pense que je pourrais réussir à avoir toutes les commandes utilisent le même fichier de configuration, et aucune autre dépendance ne devrait être nécessaire. J'ai trouvé un composant DI ici: https://github.com/luismulinari/consolefull mais cela ajoute des dépendances inutiles, j'essaie de garder cette lumière. Si je vous ai bien compris, que je pouvais faire quelque chose de similaire à ceci: demande $ = new Application ( 'Nom de l'application', 'Version', nouvelle ContainerConfig(); ); ? Je viens d'entrer dans les composants symfony, si vous pouvez répondre dans une réponse et peut-être élaborer un peu, votre solution semble correspondre à mon objectif. – stefgosselin

Répondre

1

injection manuelle

Si vous Wany de garder les choses lumière et que seul (le même) objet de configuration pour toutes les commandes, vous faites même pas contenant needa DI. Créez simplement les commandes comme ceci:

... 
$app->addCommands(array(
    new $className($configuration), 
)); 

Bien que vous deviez être conscient des compromis, par ex. vous devrez faire plus d'efforts pour l'étendre à l'avenir ou vous adapter à l'évolution des besoins.

Simple DI Container

Vous pouvez bien sûr utiliser un conteneur de DI, il y a un récipient vraiment léger appelé Twittee, qui a moins de 140 caractères (et se glisse ainsi dans un tweet). Vous pouvez simplement copier et coller cela sans ajouter de dépendance. cela peut finir dans votre cas jusqu'à la recherche semblable à:

$c = new Container(); 
$c->configA = function ($c) { 
    return new ConfigA(); 
}; 
$c->commandA = function($c) { 
    return new CommandA($c->configA()); 
} 
// ... 

Vous auriez alors besoin de mettre en place que pour toutes vos commandes et configurations et simplement pour chaque commande:

$app->addCommand($c->commandA()); 

Interface injection

Vous pouvez rouler votre propre mécanisme d'injection simple à l'aide d'interfaces et d'injection de setter. Pour chaque dépendance que vous voulez vous injecter devez définir une interface:

interface ConfigAAwareInterface { 
    public function setConfigA(ConfigA $config); 
} 
interface ConfigBAwareInterface { 
    public function setConfigA(ConfigA $config); 
} 

Toute classe qui a besoin de la dépendance peut simplement implémenter l'interface.Comme vous le plus souvent répéter les setters, faire usage d'un trait:

trait ConfigAAwareTrait { 
    private $config; 
    public function setConfigA(ConfigA $config) { $this->config = $config; } 
    public function getConfigA() { return $this->config } 
} 

class MyCommand extends Command implements ConfigAAwareInterface { 
    use ConfigAAwareTrait; 

    public function execute($in, $out) { 
     // access config 
     $this->getConfigA(); 
    } 
} 

Maintenant, tout ce qui reste est à instancier réellement les commandes et injecter les dépendances. Vous pouvez utiliser le simple « classe d'injection » suivant:

class Injector { 
    private $injectors = array(); 
    public function addInjector(callable $injector) { 
     $this->injectors[] = $injector; 
    } 
    public function inject($object) { 
     // here we'll just call the injector callables 
     foreach ($this->injectors as $inject) { 
      $inject($object); 
     } 
     return $object; 
    } 
} 

$injector = new Injector(); 

$configA = new ConfigA(); 
$injector->addInjector(function($object) use ($configA) { 
    if ($object instanceof ConfigAAwareInterface) { 
     $object->setConfigA($configA); 
    } 
}); 
// ... add more injectors 

maintenant pour construire réellement une commande, vous pouvez simplement appeler:

$injector->inject(new CommandA()); 

Et l'injecteur injecterez dépendances basé sur les interfaces mises en œuvre. Cela peut sembler à première vue un peu compliqué, mais il est en fait très utile à certains moments. Cependant, si vous avez plusieurs objets de la même classe que vous devez injecter (par exemple new Config ("path/to/a.cfg") et new Config ("path/to/b.cfg")) cela peut ne pas être le cas. être une solution idéale, car vous ne pouvez distinguer que par des interfaces.

Dépendance Bibliothèque d'injection

Vous pouvez bien sûr utiliser aussi une bibliothèque et d'ajouter que la dépendance. J'ai écrit un list of PHP dependency injection containers dans une réponse séparée.

+1

Je peux élaborer sur les alternatives de conception, si vous souhaitez en avoir. Laissez juste un commentaire. –

+0

@FalKeller - Si vous avez des idées sur le dessus de votre tête, vous pouvez ajouter que j'apprécierais sérieusement les options qui s'offrent à moi. Comme vous pouvez dans mes extraits de code voir que les commandes sont toutes chargées dynamiquement, je pourrais implémenter un mécanisme similaire pour les configurations, à savoir: avoir un fichier de configuration nommé exactement comme la commande, par exemple: TestCommand.php, TestConfig. php, et si la classe existe je l'ai mis dans $ configuration ...? Je pense juste à voix haute ici, je suis ouvert à toutes les suggestions. – stefgosselin

+1

Vous rock, monsieur. Je suis très reconnaissant pour votre perspicacité. – stefgosselin