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 ?
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
Étend votre Command class de ContainerAwareCommand et obtenez le service avec $this->getContainer()->get('my_service_id');
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);
}
}
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é.
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.
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');
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;
}
}