私はいくつかの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からの上記の回答を実装するには、それを使用する必要があります。少なくともこの方法で、コマンドを適切にユニットテストできます。
次のようにPSR-11コンテナを提供するためにContainerCommandLoaderを使用できます。
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);
}
...
注意:コンテナ設定でChangeModeを提供する必要があります。
私はsymfony2.8を代弁しています。 ContainerAwareCommandを拡張するクラスにコンストラクターを追加することはできません。拡張クラスには$this->getContainer()
があり、コンストラクターを介してサービスを注入するのではなく、サービスを取得するためにカバーしました。
あなたは$this->getContainer()->get('service-name');
を行うことができます
Services.yamlに移動します
これをファイルに追加します(例として2つの既存のサービスを使用しました)。
App\Command\MyCommand:
arguments: [
'@request_stack',
'@doctrine.orm.entity_manager'
]
ルートプロジェクトフォルダーでターミナルに入力するすべてのサービスのリストを表示するには:
php bin/console debug:autowiring --all
使用できるサービスの長いリストが表示されます。1行の例は次のようになります。
Stores CSRF tokens.
Symfony\Component\Security\Csrf\TokenStorage\TokenStorageInterface (security.csrf.token_storage)
したがって、CSRFトークンサービスが探しているものである場合(たとえば)、括弧内の部分をサービスとして使用します:(security.csrf.token_storage)
したがって、services.yamlは次のようになります。
parameters:
services:
_defaults:
autowire: true
autoconfigure: true
# Here might be some other services...
App\Command\MyCommand:
arguments: [
'@security.csrf.token_storage'
]
次に、コマンドクラスでコンストラクターのサービスを使用します。
class MyCommand extends Command
{
private $csrfToken;
public function __construct(CsrfToken $csrfToken)
{
parent::__construct();
$this->csrfToken = $csrfToken;
}
}