Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can i inject dependencies to Symfony Console commands?

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 ?

like image 724
osm Avatar asked Sep 29 '11 13:09

osm


People also ask

What is dependency injection in Symfony?

The Symfony DependencyInjection component provides a standard way to instantiate objects and handle dependency management in your PHP applications. The heart of the DependencyInjection component is a container which holds all the available services in the application.

What is Symfony Console component?

The Console component eases the creation of beautiful and testable command line interfaces. The Console component allows you to create command-line commands. Your console commands can be used for any recurring task, such as cronjobs, imports, or other batch jobs.


6 Answers

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);    
    }
}
like image 156
Isengo Avatar answered Oct 06 '22 07:10

Isengo


use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;

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

like image 27
Ramon Avatar answered Oct 06 '22 07:10

Ramon


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.

like image 22
orourkedd Avatar answered Oct 06 '22 07:10

orourkedd


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.

like image 41
Elie Nehmé Avatar answered Oct 06 '22 07:10

Elie Nehmé


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

like image 37
Olotin Temitope Avatar answered Oct 06 '22 09:10

Olotin Temitope


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;
    }
}
like image 43
Stas Sorokin Avatar answered Oct 06 '22 09:10

Stas Sorokin