Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Symfony 2: Access updated configuration from inside a command

We are creating a command that relies on other commands to generate a new database and build out its schema. So far we have successfully gotten it to read the config.yml file, add our new connection information, and to write the file back. In the same command we are then trying to run the symfony commands to create the database and schema:update. This is where we are running into problems. We get the following error:

[InvalidArgumentException] Doctrine ORM Manager named "mynewdatabase" does not exist.

If we run the command a second time there is no error because the updated configuration file is loaded fresh into the application. If we manually run the doctrine commands after writing to the config.yml file it also works without error.

We are thinking that at the point in our command where we're running the database create and update commands, it's still using the current kernel's version of the config.yml/database.yml that are stored in memory. We have tried a number of different ways to reinitialize the application/kernel configuration (calling shutdown(), boot(), etc) without luck. Here's the code:

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);
   }
}
like image 255
lewsid Avatar asked Feb 04 '13 19:02

lewsid


1 Answers

The reason this doesn't work is because the DIC goes through a compilation process and is then written to a PHP file which is then included into the current running process. Which you can see here:

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

If you change the service definitions and then try to "reboot" the kernel to compile these changes it won't include the compiled file a second time (require_once) and it will just create another instance of the already included DIC class with the old compiled service definitions.

The simplest way I can think of to get around this is to create an empty Kernel class that simply extends your AppKernel. Like so:

<?php

namespace Test\MyBundle\Command;

class FakeKernel extends \AppKernel
{
}

Then in your command, you can boot up this kernel after you've saved the new service definitions and it will re-compile a new DIC class using the "FakeKernel" name as part of the file name which means it will be included. like so:

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

Then you run your sub-commands against this new application which will be running with the new DIC:

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

disclaimer: this feels very hacky. I'm open to hearing other solutions/workarounds.

like image 84
MDrollette Avatar answered Sep 28 '22 03:09

MDrollette