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 ?
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.
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.
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);
}
}
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
Extends your Command class from ContainerAwareCommand and get the service with $this->getContainer()->get('my_service_id');
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.
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;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With