如何将依赖项注入 Symfony 控制台命令?

发布于 2024-12-07 08:41:42 字数 243 浏览 0 评论 0原文

我正在编写一个开源应用程序,使用一些 Symfony 组件,并使用 Symfony Console 组件与 shell 交互。

但是,我需要注入依赖项(在所有命令中使用),例如 Logger、Config 对象、Yaml 解析器。我通过扩展 Symfony\Component\Console\Command\Command 类解决了这个问题。但这使得单元测试变得更加困难并且看起来不正确。

我该如何解决这个问题?

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 ?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

日裸衫吸 2024-12-14 08:41:42

自 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);    
    }
}

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);    
    }
}
焚却相思 2024-12-14 08:41:42

最好不要注入容器本身,而是将容器中的服务注入到对象中。如果您使用 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 的上述答案,您必须使用它。至少这样你的命令可以被正确地进行单元测试。

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.

何以心动 2024-12-14 08:41:42
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;

ContainerAwareCommand 扩展您的 Command 类,并使用 $this->getContainer()->get('my_service_id'); 获取服务

use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;

Extends your Command class from ContainerAwareCommand and get the service with $this->getContainer()->get('my_service_id');

灰色世界里的红玫瑰 2024-12-14 08:41:42

您可以使用 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);
    }
...

注意:ChangeMode 应在容器配置中提供。

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.

Saygoodbye 2024-12-14 08:41:42

我说的是 symfony2.8。您无法将构造函数添加到扩展 ContainerAwareCommand 的类中,因为扩展类具有 $this->getContainer() ,它让您能够获取服务,而不是通过构造函数注入服务。

您可以执行 $this->getContainer()->get('service-name');

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');

乖乖公主 2024-12-14 08:41:42

转到 services.yaml

将其添加到文件中(我使用 2 个现有服务作为示例):

App\Command\MyCommand:
        arguments: [
            '@request_stack',
            '@doctrine.orm.entity_manager'
        ]

要在根项目文件夹的终端中查看所有服务类型的列表:

php bin/console debug:autowiring --all

您将获得一长串可以使用的服务,一行的示例如下所示:

 Stores CSRF tokens.
 Symfony\Component\Security\Csrf\TokenStorage\TokenStorageInterface (security.csrf.token_storage)

因此,如果 CSRF 令牌服务是您正在寻找的(例如),您将使用括号中的部分作为服务: (security.csrf.token_storage)

所以您的 services.yaml 将如下所示有点像this:

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;
    }
}

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;
    }
}
爱,才寂寞 2024-12-14 08:41:42

在 Symfony 3.4 中,如果 autowire 配置正确,则可以将服务注入到命令的构造函数中。

public function __construct(
    \AppBundle\Handler\Service\AwsS3Handler $s3Handler
) {
    parent::__construct();

    $this->s3Handler = $s3Handler;
}

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;
}
风吹短裙飘 2024-12-14 08:41:42

php 8.1
交响乐6.1

public function __construct(private EntityManagerInterface $em, string $name = null)
{
    parent::__construct($name);
}

php 8.1
Symfony 6.1

public function __construct(private EntityManagerInterface $em, string $name = null)
{
    parent::__construct($name);
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文