Search code examples
phpsymfonysymfony-3.2symfony-console

Calling service from Symfony Command fails with "The container cannot be retrieved as the application instance is not yet set." error, why?


I have the following services definition at IntegrationBundle/Resources/config/services.yml:

services:
  event.subscriber.params.extractor:
    class: IntegrationBundle\Middleware\EventSuscriberParamsExtractor

  main.api:
    class: IntegrationBundle\API\MainAPI
    calls:
      - [setContainer, ['@service_container']]
  order.push.api:
    class: IntegrationBundle\API\OrderPushAPI
    calls:
      - [setContainer, ['@service_container']]

  process.account.api:
    class: IntegrationBundle\API\ProcessAccountData
    arguments:
      - '@event.subscriber.params.extractor'
    calls:
      - [setContainer, ['@service_container']]
  ...

I need to access main.api from within a Symfony Command and this is how I am doing it:

namespace IntegrationBundle\Command;

use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Command\LockableTrait;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

class SchneiderAPICommand extends ContainerAwareCommand
{
    use LockableTrait;

    protected function configure()
    {
        $this
            ->setName('api:execute')
            ->setDefinition(
                new InputDefinition([
                    new InputOption('source', '', InputArgument::OPTIONAL, '', 'Wonderware'),
                    new InputOption('object', '', InputArgument::OPTIONAL, '', 'Account,AgreementHistory'),
                    new InputOption('quantity', '', InputArgument::OPTIONAL, '', $this->getContainer()->getParameter('api_items_to_process')
                    ),
                ])
            );
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        // Prevent multiple executions of a console command
        if (!$this->lock()) {
            $output->writeln('The command is already running in another process.');

            return 0;
        }

        $output->writeln($this->getContainer()->get('main.api')->execute(
            $input->getOption('source'),
            str_replace(',', '|', $input->getOption('object')),
            $input->getOption('quantity')
        ));
    }
}

Then I am trying the command as follow:

$ bin/console api:execute --source=Wonderware --object=Account --quantity=50 -vvv  

  [LogicException]                                                               
  The container cannot be retrieved as the application instance is not yet set.  

As you can see is failing and I am not sure what I am missing here. Below is a stack trace (if that help somehow):

Exception trace:
 () at /var/www/html/symfony/vendor/symfony/symfony/src/Symfony/Bundle/FrameworkBundle/Command/ContainerAwareCommand.php:40
 Symfony\Bundle\FrameworkBundle\Command\ContSchneiderainerAwareCommand->getContainer() at /var/www/html/symfony/src/IntegrationBundle/Command/MainAPICommand.php:38
 IntegrationBundle\Command\MainAPICommand->configure() at /var/www/html/symfony/vendor/symfony/symfony/src/Symfony/Component/Console/Command/Command.php:63
 Symfony\Component\Console\Command\Command->__construct() at n/a:n/a
 ReflectionClass->newInstance() at /var/www/html/symfony/vendor/symfony/symfony/src/Symfony/Component/HttpKernel/Bundle/Bundle.php:193
 Symfony\Component\HttpKernel\Bundle\Bundle->registerCommands() at /var/www/html/symfony/vendor/symfony/symfony/src/Symfony/Bundle/FrameworkBundle/Console/Application.php:135
 Symfony\Bundle\FrameworkBundle\Console\Application->registerCommands() at /var/www/html/symfony/vendor/symfony/symfony/src/Symfony/Bundle/FrameworkBundle/Console/Application.php:108
 Symfony\Bundle\FrameworkBundle\Console\Application->all() at /var/www/html/symfony/vendor/symfony/symfony/src/Symfony/Bundle/FrameworkBundle/Console/Application.php:72
 Symfony\Bundle\FrameworkBundle\Console\Application->doRun() at /var/www/html/symfony/vendor/symfony/symfony/src/Symfony/Component/Console/Application.php:124
 Symfony\Component\Console\Application->run() at /var/www/html/symfony/bin/console:28

I have been reading here (I do not want and do not need to define this command as a service), here, here and here but none of them help me.

Can any give me some help here? What I am missing?


Solution

  • As per my first comment, you cannot call getContainer from configure() as the container has not yet been injected since configure is called directly from the constructor. You will need to pull the parameter in the execute method.

    An alternative (and perhaps a bit cleaner) approach is to define the command as a service and inject the services actually needed. http://symfony.com/doc/current/console/commands_as_services.html

    For example:

    class SchneiderAPICommand extends Command
    {
        private $mainAPI;
        private $defaultQuaity;
        public function __construct(MainAPI $mainAPI, $defaultQuantity)
        {
            parent::__construct();
            $this->mainAPI = $mainAPI;
            $this->defaultQuanity = $defaultQuanity;
        }
        protected function configure() {
            ...
            new InputOption('quantity', '', InputArgument::OPTIONAL, '', $this->defaultQuantity),
    
    # services.yml
    main.api.command:
        class: MyBundle\Command\SchneiderAPICommand
        tags: [{ name: console.command }] # This makes it a command
        arguments: ['@main.api','%api_items_to_process%']