Ответ 1
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
Расширяет класс Command от ContainerAwareCommand и получает сервис с $this->getContainer()->get('my_service_id');
Я пишу приложение с открытым исходным кодом, использующее некоторые компоненты Symfony, и используя компонент Symfony Console для взаимодействия с оболочкой.
Но мне нужно вводить зависимости (используемые во всех командах) что-то вроде Logger, объекта Config, парсеров Yaml. Я решил эту проблему с расширением класса Symfony\Component\Console\Command\Command
. Но это делает модульное тестирование сложнее и выглядит неправильно.
Как я могу это решить?
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
Расширяет класс Command от ContainerAwareCommand и получает сервис с $this->getContainer()->get('my_service_id');
Начиная с Symfony 4.2, ContainerAwareCommand устарела. Вместо этого используйте 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);
}
}
Лучше всего не вводить сам контейнер, а вводить сервисы из контейнера в ваш объект. Если вы используете контейнер Symfony2, вы можете сделать что-то вроде этого:
MyBundle/Resources/config/services (или где вы решите разместить этот файл):
...
<services>
<service id="mybundle.command.somecommand" class="MyBundle\Command\SomeCommand">
<call method="setSomeService">
<argument type="service" id="some_service_id" />
</call>
</service>
</services>
...
Тогда ваш класс команд должен выглядеть так:
<?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;
}
...
Я знаю, что вы сказали, что не используете контейнер инъекций зависимостей, но для реализации вышеупомянутого ответа от @ramon вы должны его использовать. По крайней мере таким образом ваша команда может быть правильно протестирована.
Так как Symfony 3.3 (май 2017) вы легко можете использовать Dependency Injection в командах.
См. мой полный ответ здесь (чтобы предотвратить дублирование): fooobar.com/questions/159731/...
Вы можете использовать ContainerCommandLoader для предоставления контейнера PSR-11 следующим образом:
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 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 должен быть указан в конфигурации контейнера.
Я говорю за symfony2.8. Вы не можете добавить конструктор к классу, который расширяет ContainerAwareCommand, потому что у расширенного класса есть $this->getContainer()
, который помог вам получить ваши услуги, а не внедрять их через конструктор.
Вы можете сделать $this->getContainer()->get('service-name');