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 extendi开发者_JS百科ng Symfony\Component\Console\Command\Command
class. But this makes unit testing harder and not looks correct way.
How can i solve this ?
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);
}
}
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.
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
Extends your Command class from ContainerAwareCommand and get the service with $this->getContainer()->get('my_service_id');
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.
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');
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;
}
}
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;
}
php 8.1 Symfony 6.1
public function __construct(private EntityManagerInterface $em, string $name = null)
{
parent::__construct($name);
}
精彩评论