使用依赖注入在控制台命令中加载自定义配置
Load custom configuration in a console command using dependency-injection
我已经开始使用 Symfony 的控制台组件来构建各种 cli 工具。
我目前正在拼凑这样一个控制台应用程序,它需要各种配置,其中一些配置在命令之间共享,其他配置是该命令独有的。
起初我使用的是辅助程序 class,通过静态函数调用来加载常规配置数组。
昨天我重构了这个,现在在配置组件中加载配置,以及用于验证的 treeBuilder 机制。这一切都在主控制台脚本中完成,而不是在“命令”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();
目前,设置配置的唯一方法是在单独的 class 中设置加载配置的代码,并在每个方法的 configure() 方法中调用此 class。
也许我错过了一种更“symfonyish”的方法,我也想避免在代码库中使用整个框架,这意味着是一个轻量级的控制台应用程序。
有没有办法使用 DI 或其他我不知道的方法将处理后的配置传递给调用的命令?
手动注入
如果你想让事情变得简单并且所有命令只有一个(相同的)配置对象,你甚至不需要 DI 容器。只需像这样创建命令:
...
$app->addCommands(array(
new $className($configuration),
));
尽管您必须意识到权衡取舍,例如将来您将不得不付出更多努力来扩展它或适应不断变化的需求。
简单 DI 容器
你当然可以使用 DI 容器,有一个非常轻量级的容器叫做 Twittee, which has less than 140 characters (and thus fits in a tweet)。您可以简单地复制并粘贴它并且不添加任何依赖项。在您的情况下,这可能最终看起来类似于:
$c = new Container();
$c->configA = function ($c) {
return new ConfigA();
};
$c->commandA = function($c) {
return new CommandA($c->configA());
}
// ...
然后您需要为所有命令和配置设置它,然后只需为每个命令设置:
$app->addCommand($c->commandA());
接口注入
您可以使用接口和 setter 注入来推出自己的简单注入机制。对于要注入的每个依赖项,您需要定义一个接口:
interface ConfigAAwareInterface {
public function setConfigA(ConfigA $config);
}
interface ConfigBAwareInterface {
public function setConfigA(ConfigA $config);
}
任何需要依赖的 class 都可以简单地实现接口。由于您将主要重复 setters,因此请使用特征:
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();
}
}
现在剩下的就是实际实例化命令并注入依赖项。您可以使用以下简单的 "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
现在要实际构建命令,您只需调用:
$injector->inject(new CommandA());
并且注入器将根据已实现的接口注入依赖项。
这乍一看可能有点复杂,但实际上有时非常有用。
但是,如果您有多个相同 class 的对象需要注入(例如 new Config("path/to/a.cfg") 和 new Config("path/to/b.cfg")),这可能不是一个理想的解决方案,因为只能通过接口来区分。
依赖注入库
您当然也可以使用整个库并将其添加为依赖项。我在单独的回答里写了一个list of PHP dependency injection containers
我已经开始使用 Symfony 的控制台组件来构建各种 cli 工具。
我目前正在拼凑这样一个控制台应用程序,它需要各种配置,其中一些配置在命令之间共享,其他配置是该命令独有的。
起初我使用的是辅助程序 class,通过静态函数调用来加载常规配置数组。
昨天我重构了这个,现在在配置组件中加载配置,以及用于验证的 treeBuilder 机制。这一切都在主控制台脚本中完成,而不是在“命令”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();
目前,设置配置的唯一方法是在单独的 class 中设置加载配置的代码,并在每个方法的 configure() 方法中调用此 class。
也许我错过了一种更“symfonyish”的方法,我也想避免在代码库中使用整个框架,这意味着是一个轻量级的控制台应用程序。
有没有办法使用 DI 或其他我不知道的方法将处理后的配置传递给调用的命令?
手动注入
如果你想让事情变得简单并且所有命令只有一个(相同的)配置对象,你甚至不需要 DI 容器。只需像这样创建命令:
...
$app->addCommands(array(
new $className($configuration),
));
尽管您必须意识到权衡取舍,例如将来您将不得不付出更多努力来扩展它或适应不断变化的需求。
简单 DI 容器
你当然可以使用 DI 容器,有一个非常轻量级的容器叫做 Twittee, which has less than 140 characters (and thus fits in a tweet)。您可以简单地复制并粘贴它并且不添加任何依赖项。在您的情况下,这可能最终看起来类似于:
$c = new Container();
$c->configA = function ($c) {
return new ConfigA();
};
$c->commandA = function($c) {
return new CommandA($c->configA());
}
// ...
然后您需要为所有命令和配置设置它,然后只需为每个命令设置:
$app->addCommand($c->commandA());
接口注入
您可以使用接口和 setter 注入来推出自己的简单注入机制。对于要注入的每个依赖项,您需要定义一个接口:
interface ConfigAAwareInterface {
public function setConfigA(ConfigA $config);
}
interface ConfigBAwareInterface {
public function setConfigA(ConfigA $config);
}
任何需要依赖的 class 都可以简单地实现接口。由于您将主要重复 setters,因此请使用特征:
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();
}
}
现在剩下的就是实际实例化命令并注入依赖项。您可以使用以下简单的 "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
现在要实际构建命令,您只需调用:
$injector->inject(new CommandA());
并且注入器将根据已实现的接口注入依赖项。 这乍一看可能有点复杂,但实际上有时非常有用。 但是,如果您有多个相同 class 的对象需要注入(例如 new Config("path/to/a.cfg") 和 new Config("path/to/b.cfg")),这可能不是一个理想的解决方案,因为只能通过接口来区分。
依赖注入库
您当然也可以使用整个库并将其添加为依赖项。我在单独的回答里写了一个list of PHP dependency injection containers