Symfony 2:从命令内部访问更新的配置


Symfony 2: Access updated configuration from inside a command

我们正在创建一个命令,该命令依赖于其他命令来生成新数据库并构建其模式。到目前为止,我们已经成功地让它读取config.yml文件,添加新的连接信息,并写回文件。在同一个命令中,我们尝试运行symfony命令来创建数据库和模式:update。这就是我们遇到问题的地方。我们得到以下错误:

〔InvalidArgumentException〕命名为"mynewdatabase"的条令ORM管理器不存在。

如果我们再次运行该命令,则不会出现错误,因为更新后的配置文件是新加载到应用程序中的。如果我们在写入config.yml文件后手动运行条令命令,它也可以正常工作。

我们认为,在我们运行数据库创建和更新命令的命令中,它仍然使用存储在内存中的当前内核版本的config.yml/database.yml。我们尝试了许多不同的方法来重新初始化应用程序/内核配置(调用shutdown()、boot()等),但没有成功。这是代码:

namespace Test'MyBundle'Command;
use Symfony'Bundle'FrameworkBundle'Command'ContainerAwareCommand;
use Symfony'Component'Console'Input'InputArgument;
use Symfony'Component'Console'Input'InputInterface;
use Symfony'Component'Console'Input'InputOption;
use Symfony'Component'Console'Input'ArrayInput;
use Symfony'Component'Console'Output'OutputInterface;
use Symfony'Component'Yaml'Yaml;
class GeneratorCommand extends ContainerAwareCommand
{
    protected function configure()
    {
        $this
           ->setName('generate')
           ->setDescription('Create a new database.')
           ->addArgument('dbname', InputArgument::REQUIRED, 'The db name')
        ;
    }
   /*
      example: php app/console generate mynewdatabase
   */
   protected function execute(InputInterface $input, OutputInterface $output)
   {
      //Without this, the doctrine commands will prematurely end execution
      $this->getApplication()->setAutoExit(false);
      //Open up app/config/config.yml
      $yaml = Yaml::parse(file_get_contents($this->getContainer()->get('kernel')->getRootDir() .'/config/config.yml'));
      //Take input dbname and use it to name the database
      $db_name = $input->getArgument('dbname');
      //Add that connection to app/config/config.yml
      $yaml['doctrine']['dbal']['connections'][$site_name] = Array('driver' => '%database_driver%', 'host' => '%database_host%', 'port' => '%database_port%', 'dbname' => $site_name, 'user' => '%database_user%', 'password' => '%database_password%', 'charset' => 'UTF8');
      $yaml['doctrine']['orm']['entity_managers'][$site_name] = Array('connection' => $site_name, 'mappings' => Array('MyCustomerBundle' => null));
      //Now put it back
      $new_yaml = Yaml::dump($yaml, 5);
      file_put_contents($this->getContainer()->get('kernel')->getRootDir() .'/config/config.yml', $new_yaml);
      /* http://symfony.com/doc/current/components/console/introduction.html#calling-an-existing-command */
      //Set up our db create script arguments
      $args = array(
         'command'      => 'doctrine:database:create',
         '--connection'   => $site_name,
      );
      $db_create_input = new ArrayInput($args);
      //Run the symfony database create arguments
      $this->getApplication()->run($db_create_input, $output);
      //Set up our schema update script arguments
      $args = array(
         'command'   => 'doctrine:schema:update',
         '--em'      => $site_name,
         '--force'   => true
      );
      $update_schema_input = new ArrayInput($args);
      //Run the symfony database create command
      $this->getApplication()->run($update_schema_input, $output);
   }
}

这不起作用的原因是DIC经过一个编译过程,然后被写入一个PHP文件,该文件随后被包含在当前运行的过程中。你可以在这里看到:

https://github.com/symfony/symfony/blob/master/src/Symfony/Component/HttpKernel/Kernel.php#L562

如果您更改服务定义,然后尝试"重新启动"内核以编译这些更改,它将不会第二次包含已编译的文件(require_one),它只会使用旧的已编译服务定义创建已包含DIC类的另一个实例。

我能想到的解决这个问题的最简单的方法是创建一个空的Kernel类,它只是扩展你的AppKernel。像这样:

<?php
namespace Test'MyBundle'Command;
class FakeKernel extends 'AppKernel
{
}

然后,在您的命令中,您可以在保存新的服务定义后启动该内核,它将使用"FakeKernel"名称作为文件名的一部分重新编译一个新的DIC类,这意味着它将被包含在内。像这样:

$kernel = new 'Test'MyBundle'Command'FakeKernel($input->getOption('env'), true);
$application = new 'Symfony'Bundle'FrameworkBundle'Console'Application($kernel);

然后,您针对这个新应用程序运行子命令,该应用程序将使用新的DIC:运行

$application->run($db_create_input, $output);

免责声明:这感觉很粗糙。我愿意听取其他解决方案/解决方案