使用依赖注入在控制台命令中加载自定义配置

时间:2015-02-27 15:14:04

标签: symfony symfony-2.1

我已经开始使用symfony的控制台组件来构建各种cli工具。

我目前正在把这样一个控制台应用程序拼凑在一起,它需要各种配置,其中一些是命令共享的,其他配置对命令来说是独一无二的。

起初我使用了一个辅助类,通过静态函数调用来加载常规配置数组。

昨天我重构了这个,现在在配置组件中加载配置,以及用于验证的treeBuilder机制。这都是在主控制台脚本中完成的,而不是在“命令”类中完成的。

$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();

目前,设置配置的唯一方法是设置在单独的类中加载配置的代码,并在每个方法的configure()方法中调用此类。

也许有一种更为“symfonyish”的做法,我错过了,我也想避免在代码库中使用整个framwework,这是一个轻量级的控制台应用程序。

有没有办法将处理过的配置传递给被调用的命令,使用DI或其他一些我不知道的方法?

1 个答案:

答案 0 :(得分:1)

手动注入

如果您想要保持简洁并且只有一个(相同的)配置对象用于所有命令,那么您甚至不需要DI容器。只需创建如下命令:

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

虽然您必须了解权衡,例如您将来必须付出更多努力来扩展这一点,或者根据不断变化的要求进行调整。

简单DI容器

你当然可以使用一个DI容器,有一个名为Twittee的非常轻量级的容器,它有少于140个字符(因此适合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);
}

任何需要依赖关系的类都可以简单地实现该接口。因为你将主要重复设置者,使用一个特征:

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();
    }
}

现在剩下的就是实际实例化命令并注入依赖项。您可以使用以下简单的"注入器类":

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());

注入器将根据实现的接口注入依赖项。 这可能起初看起来有点复杂,但事实上它实际上非常有用。 但是,如果您需要注入多个相同类的对象(例如,新配置("路径/到/ a.cfg")和新配置("路径/到/ b)。 cfg"))这可能不是一个理想的解决方案,因为你只能通过接口进行区分。

依赖注入库

您当然也可以使用整个库并将其添加为依赖项。我在一个单独的答案中写了list of PHP dependency injection containers