11

我有一个命令行应用程序,到目前为止它使用 Symfony 依赖注入组件。我现在发现我想添加命令行选项并改进输出格式,而 Symfony 控制台组件似乎是一个不错的选择。

但是,我无法理解如何让 Symfony 控制台命令类接收容器对象。

我发现的文档使用了 ContainerAwareCommand 类,但它来自 FrameworkBundle——这似乎是添加到纯 CLI 应用程序的大量开销,因为它需要进一步的捆绑包,例如路由、http、配置、缓存等,在这里,这些都与我无关。

(现有的 SO 问题我如何将依赖项注入 Symfony 控制台命令?也假设 FrameworkBundle,顺便说一句。)

我在这里创建了一个测试存储库,其中包含一个说明问题的基本命令:https ://github.com/joachim-n/console-with-di

4

3 回答 3

7

Symfony 3/4/5 方式

由于 2018 和Symfony 3.4+ DI 功能,您可以将命令用作服务

感谢@TravisCarden,您可以在这里找到工作演示

简而言之:

1.应用内核

<?php

# app/Kernel.php

namespace App;

use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\HttpKernel\Kernel;
use Symfony\Component\DependencyInjection\ContainerBuilder;

final class AppKernel extends Kernel
{
    public function registerBundles(): array
    {
        return [];
    }

    public function registerContainerConfiguration(LoaderInterface $loader): void
    {
        $loader->load(__DIR__.'/../config/services.yml');
    }

    protected function build(ContainerBuilder $containerBuilder): void
    {
        $containerBuilder->addCompilerPass($this->createCollectingCompilerPass());
    }

    private function createCollectingCompilerPass(): CompilerPassInterface
    {
        return new class implements CompilerPassInterface
        {
            public function process(ContainerBuilder $containerBuilder)
            {
                $applicationDefinition = $containerBuilder->findDefinition(Application::class);

                foreach ($containerBuilder->getDefinitions() as $definition) {
                    if (! is_a($definition->getClass(), Command::class, true)) {
                        continue;
                    }

                    $applicationDefinition->addMethodCall('add', [new Reference($definition->getClass())]);
                }
            }
        };
    }
}

2. 服务

# config/services.yml

services:
    _defaults:
        autowire: true

    App\:
        resource: '../app'

    Symfony\Component\Console\Application:
        public: true

3. Bin 文件

# index.php

require_once __DIR__ . '/vendor/autoload.php';

use Symfony\Component\Console\Application;

$kernel = new AppKernel;
$kernel->boot();

$container = $kernel->getContainer();
$application = $container->get(Application::class)
$application->run();

运行

php index.php

如果你对更详细的解释感兴趣,我写了一篇文章Why You Should Combine Symfony Console and Dependency Injection

于 2018-05-15T17:54:38.133 回答
3

是的,不需要整个框架。在您的情况下,首先您需要创建一种入口脚本。像这样的东西:

<?php

require 'just/set/your/own/path/to/vendor/autoload.php';

use Symfony\Component\Console\Application;
use Symfony\Component\DependencyInjection\ContainerBuilder;

$container = new ContainerBuilder();
$container
    ->register('your_console_command', 'Acme\Command\YourConsoleCommand')
    ->addMethodCall('setContainer', [new Reference('service_container')]);
$container->compile();

$application = new Application();
$application->add($container->get('your_console_command'));
$application->run();

在此示例中,我们创建容器,然后将命令注册为服务,向命令添加依赖项(在我们的示例中是整个容器 - 但显然您可以创建另一个依赖项并注入它)并编译容器。然后我们只需创建应用程序,将命令实例添加到应用程序并运行它。

当然,您可以将容器的所有配置保留为yaml甚至xml使用 PHP 格式。

于 2017-05-18T21:02:20.093 回答
0

四年后,但适用于寻找类似解决方案的人。我有一个相似的场景,并使用依赖注入实现了我自己的样板,准备用于独立的 symfony 控制台应用程序,支持命令和服务的自动装配和自动配置。

composer create-project coral-media/crune crune

接收容器的示例命令。Github上提供的源代码

快乐编码!

于 2022-02-09T07:41:21.577 回答