46

I'm writing an open source application uses some Symfony components, and using Symfony Console component for interacting with shell.

But, i need to inject dependencies (used in all commands) something like Logger, Config object, Yaml parsers.. I solved this problem with extending Symfony\Component\Console\Command\Command class. But this makes unit testing harder and not looks correct way.

How can i solve this ?

Mohammed H
  • 6,880
  • 16
  • 81
  • 127
osm
  • 4,186
  • 3
  • 23
  • 24

8 Answers8

45

Since Symfony 4.2 the ContainerAwareCommand is deprecated. Use the DI instead.

namespace App\Command;

use Symfony\Component\Console\Command\Command;
use Doctrine\ORM\EntityManagerInterface;

final class YourCommand extends Command
{
    /**
     * @var EntityManagerInterface
     */
    private $entityManager;

    public function __construct(EntityManagerInterface $entityManager)
    {
        $this->entityManager = $entityManager;

        parent::__construct();
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        // YOUR CODE
        $this->entityManager->persist($object1);    
    }
}
Tomas Votruba
  • 23,240
  • 9
  • 79
  • 115
Isengo
  • 2,004
  • 3
  • 21
  • 43
  • 4
    This is the best solution at the moment! – Tomas Votruba Feb 19 '19 at 17:49
  • 3
    The Command parent constructor has `string $name = null` as its first argument, that should be put in before any injected service. – Sam Wilson May 01 '19 at 00:49
  • `parent::__construct()` needs `$name` as parameter, e.g. `parent::__construct(self::$defaultName)` @SamWilson it doesn't need to be provided as argument of `YourCommand` – auipga May 16 '19 at 14:16
18

It is best not to inject the container itself but to inject services from the container into your object. If you're using Symfony2's container, then you can do something like this:

MyBundle/Resources/config/services (or wherever you decide to put this file):

...
    <services>
        <service id="mybundle.command.somecommand" class="MyBundle\Command\SomeCommand">
        <call method="setSomeService">
             <argument type="service" id="some_service_id" />
        </call>
        </service>
    </services>
...

Then your command class should look like this:

<?php
namespace MyBundle\Command;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use The\Class\Of\The\Service\I\Wanted\Injected;

class SomeCommand extends Command
{
   protected $someService;
   public function setSomeService(Injected $someService)
   {
       $this->someService = $someService;
   }
...

I know you said you're not using the dependency injection container, but in order to implement the above answer from @ramon, you have to use it. At least this way your command can be properly unit tested.

orourkedd
  • 6,201
  • 5
  • 43
  • 66
  • I've done this way but it was not working because the command service was not registered. In order to do that, see [this answer to another question](http://stackoverflow.com/questions/16710969/symfony-tagging-a-command-with-console-command-doesnt-seem-to-work/16723785#16723785). – Gabriel Gcia Fdez Jul 09 '13 at 11:08
  • As of Symfony 2.4, you will be able to do things differently, by registering your command as a service and using the `console.command` tag – Robbert van den Bogerd Oct 16 '13 at 08:36
  • 1
    @andho What's wrong with registering command as a service to explicitly define its dependencies? – Dmitry May 01 '14 at 18:08
  • It's rather a pain to have to write a service setter method for every single service you want injected into the command class. – joachim May 01 '17 at 14:15
17
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;

Extends your Command class from ContainerAwareCommand and get the service with $this->getContainer()->get('my_service_id');

pamil
  • 980
  • 2
  • 10
  • 20
Ramon
  • 338
  • 4
  • 3
  • 38
    This is *factually incorrect*. Using this, you are *not* "injecting dependencies" as the question asks - you're injecting a service locator, which then functions to retrieve these services. (Anti-pattern). – Jimbo Mar 24 '17 at 10:20
  • 14
    THIS IS OUTDATED SOLUTION in 2017+/Symfony 3.3+ – Tomas Votruba Feb 19 '19 at 17:50
4

You can use ContainerCommandLoader in order to provide a PSR-11 container as follow:

require 'vendor/autoload.php';

$application = new Application('my-app', '1.0');

$container = require 'config/container.php';

// Lazy load command with container
$commandLoader = new ContainerCommandLoader($container, [
    'app:change-mode' => ChangeMode::class,
    'app:generate-logs' => GenerateLogos::class,
]);

$application->setCommandLoader($commandLoader);

$application->run();

ChangeMode class could be defined as follow:

class ChangeMode extends Command
{

    protected static $defaultName = 'app:change-mode';

    protected $container;

    public function __construct(ContainerInterface $container)
    {
        $this->container = $container;
        parent::__construct(static::$defaultName);
    }
...

NB.: ChangeMode should be provided in the Container configuration.

Elie Nehmé
  • 229
  • 2
  • 5
1

I'm speaking for symfony2.8. You cannot add a constructor to the class that extends the ContainerAwareCommand because the extended class has a $this->getContainer() which got you covered in getting your services instead of injecting them via the constructor.

You can do $this->getContainer()->get('service-name');

Olotin Temitope
  • 419
  • 6
  • 13
1

Go to services.yaml

Add This to the file(I used 2 existing services as an example):

App\Command\MyCommand:
        arguments: [
            '@request_stack',
            '@doctrine.orm.entity_manager'
        ]

To see a list of all services type in terminal at the root project folder:

php bin/console debug:autowiring --all

You will get a long list of services you can use, an example of one line would look like this:

 Stores CSRF tokens.
 Symfony\Component\Security\Csrf\TokenStorage\TokenStorageInterface (security.csrf.token_storage)

So if CSRF token services is what you are looking for(for example) you will use as a service the part in the parenthesis: (security.csrf.token_storage)

So your services.yaml will look somewhat like this:

parameters:

services:
    _defaults:
        autowire: true      
        autoconfigure: true 

# Here might be some other services...

App\Command\MyCommand:
        arguments: [
            '@security.csrf.token_storage'
        ]

Then in your command class use the service in the constructor:

class MyCommand extends Command
{
    private $csrfToken;

    public function __construct(CsrfToken $csrfToken)
    {
        parent::__construct();
        $this->csrfToken = $csrfToken;
    }
}
Stas Sorokin
  • 3,029
  • 26
  • 18
0

In Symfony 3.4, if autowire is configured correctly, services can be injected into the constructor of the command.

public function __construct(
    \AppBundle\Handler\Service\AwsS3Handler $s3Handler
) {
    parent::__construct();

    $this->s3Handler = $s3Handler;
}
cluisalvarado
  • 13
  • 1
  • 5
0

php 8.1 Symfony 6.1

public function __construct(private EntityManagerInterface $em, string $name = null)
{
    parent::__construct($name);
}
Agafonov
  • 11
  • 1