I have started using Symfony's console components to build various cli tools.
I am currently slapping together such a console app, that has require various configurations, some of which are shared among commands, other configs are unique to the command.
At first I was using a helper class, with a static function call to load a regular configuration array.
Yesterday I refactored this and now load configuration in the config component, along with the treeBuilder mechanism for validation. This is all done in the main console script, not in the "command" classes.
$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();
Currently, the only means to setup the configuration is to setup the code that loads the configuration in a separate class and call this class in in the configure() method of every method.
Maybe there is a more "symfonyish" way of doing this that I missed, I also would like to avoid having the entire framework in codebase, this is meant to be a lightweight console app.
Is there a way to pass the processed configuration to the commands being invoked, using DI or some other method I am not aware of?
Manual Injection
If you wany to keep things light and only have one (the same) configuration object for all commands, you don't even needa DI container. Simply create the commands like this:
...
$app->addCommands(array(
new $className($configuration),
));
Although you have to be aware of the trade-offs, e.g. you will have to have more effort extending this in the future or adjust to changing requirements.
Simple DI Container
You can of course use a DI container, there is a really lightweight container called Twittee, which has less than 140 characters (and thus fits in a tweet). You could simply copy and paste that and add no dependency. In your case this may end up looking similar to:
$c = new Container();
$c->configA = function ($c) {
return new ConfigA();
};
$c->commandA = function($c) {
return new CommandA($c->configA());
}
// ...
You then would need to set that up for all your commands and configurations and then simply for each command:
$app->addCommand($c->commandA());
Interface Injection
You could roll your own simple injection mechanism using interfaces and setter injection. For each dependency you want to inject you will need to define an interface:
interface ConfigAAwareInterface {
public function setConfigA(ConfigA $config);
}
interface ConfigBAwareInterface {
public function setConfigA(ConfigA $config);
}
Any class that needs the dependency can simply implement the interface. As you will mostly repeat the setters, make use of a 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();
}
}
Now all that is left is to actually instantiate the commands and inject the dependencies. You can use the following simple "injector class":
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
Now to actually construct a command, you can simply call:
$injector->inject(new CommandA());
And the injector will inject dependencies based on the implemented interfaces. This may at first seem a little complicated, but it is in fact quite helpful at times. However, if you have multiple objects of the same class that you need to inject (e.g. new Config("path/to/a.cfg") and new Config("path/to/b.cfg")) this might not be an ideal solution, as you can only distinguish by interfaces.
Dependency Injection Library
You can of course also use a whole library and add that as dependency. I have written a list of PHP dependency injection containers in a separate answer.