web-dev-qa-db-fra.com

Comment puis-je injecter des dépendances dans les commandes de la console Symfony?

J'écris une application open source utilise certains composants Symfony et utilise le composant Symfony Console pour interagir avec Shell.

Mais, j'ai besoin d'injecter des dépendances (utilisées dans toutes les commandes) quelque chose comme Logger, Config object, Yaml parsers .. J'ai résolu ce problème en étendant Symfony\Component\Console\Command\Command classe. Mais cela rend les tests unitaires plus difficiles et ne semble pas correct.

Comment puis-je resoudre ceci ?

36
osm
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;

Étend votre Command class de ContainerAwareCommand et obtenez le service avec $this->getContainer()->get('my_service_id');

23
Ramon

Depuis Symfony 4.2 le ContainerAwareCommand est obsolète. Utilisez plutôt la DI.

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);    
    }
}
28
Isengo

Il est préférable de ne pas injecter le conteneur lui-même mais d'injecter les services du conteneur dans votre objet. Si vous utilisez le conteneur de Symfony2, vous pouvez faire quelque chose comme ceci:

MyBundle/Resources/config/services (ou partout où vous décidez de mettre ce fichier):

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

Ensuite, votre classe de commande devrait ressembler à ceci:

<?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;
   }
...

Je sais que vous avez dit que vous n'utilisez pas le conteneur d'injection de dépendances, mais pour implémenter la réponse ci-dessus de @ramon, vous devez l'utiliser. Au moins de cette façon, votre commande peut être correctement testée à l'unité.

17
orourkedd

Vous pouvez utiliser ContainerCommandLoader afin de fournir un conteneur PSR-11 comme suit:

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

La classe ChangeMode peut être définie comme suit:

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 doit être fourni dans la configuration du conteneur.

2
Elie Nehmé

Je parle pour symfony2.8. Vous ne pouvez pas ajouter un constructeur à la classe qui étend le ContainerAwareCommand car la classe étendue a une $this->getContainer() qui vous a permis d'obtenir vos services au lieu de les injecter via le constructeur.

Vous pouvez faire $this->getContainer()->get('service-name');

1
Olotin Temitope

Accédez à services.yaml

Ajoutez ceci au fichier (j'ai utilisé 2 services existants comme exemple):

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

Pour voir une liste de tous les services, tapez dans le terminal dans le dossier racine du projet:

php bin/console debug:autowiring --all

Vous obtiendrez une longue liste de services que vous pouvez utiliser, un exemple d'une ligne ressemblerait à ceci:

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

Donc, si les services de jetons CSRF sont ce que vous recherchez (par exemple), vous utiliserez comme service la partie entre parenthèses: (security.csrf.token_storage)

Ainsi, votre service services.yaml ressemblera un peu à ceci:

parameters:

services:
    _defaults:
        autowire: true      
        autoconfigure: true 

# Here might be some other services...

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

Ensuite, dans votre classe de commande, utilisez le service dans le constructeur:

class MyCommand extends Command
{
    private $csrfToken;

    public function __construct(CsrfToken $csrfToken)
    {
        parent::__construct();
        $this->csrfToken = $csrfToken;
    }
}
0
Stas Sorokin