【问题标题】:How to use DependencyInjection from symfony in stand alone application with commands?如何在带有命令的独立应用程序中使用来自 symfony 的 DependencyInjection?
【发布时间】:2021-10-15 16:19:35
【问题描述】:

我一直在使用 symfony/console 来制作命令并像这样注册它们,一切正常:

bin/控制台:

#!/usr/bin/env php
<?php
require_once __DIR__ . '/../vendor/autoload.php';

use App\Commands\LocalitiesCommand;
use Symfony\Component\Console\Application;

$app = new Application();
$app->add(new LocalitiesCommand(new LocalitiesGenerator()));
$app->run();

src/Commands/LocalitiesCommand.php:

<?php

declare(strict_types=1);

namespace App\Commands;

use App\LocalitiesGenerator;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;


final class LocalitiesCommand extends Command
{
    protected static $defaultName = 'app:generate-localities';

    public function __construct(private LocalitiesGenerator $localitiesGenerator)
    {
        parent::__construct();
    }

    protected function configure(): void
    {
        $this
            ->setDescription('Generate localities.json file')
            ->setHelp('No arguments needed.');
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $this->localitiesGenerator->generateJsonLocalities();
        $output->writeln("File localities.json generated!");
        return Command::SUCCESS;
    }
}

现在我想用 symfony/dependency-injection 自动注入服务,我正在阅读文档并做了一些更改:

新的bin/console:

#!/usr/bin/env php
<?php
require_once __DIR__ . '/../vendor/autoload.php';

use App\Commands\LocalitiesCommand;
use Symfony\Component\Console\Application;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use Symfony\Component\Config\FileLocator;

$container = new ContainerBuilder();
$loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/src/config'));
$loader->load('services.yaml');
$container->compile();


$app = new Application();
$app->add(new LocalitiesCommand());
$app->run();

config/services.yaml:

services:
  _defaults:
    autowire: true
    autoconfigure: true
    public: false

但是当我实例化我的命令时,仍然要求我在构造函数中添加我的服务。为什么它不起作用?

【问题讨论】:

  • 你在用 composer 吗?
  • @RudyDavid 是的,我正在使用作曲家
  • @yivi 可能我看过一些教程,而且 symfony 允许将服务注入到命令中,你可以在这里查看:symfony.com/doc/current/…
  • @yivi Aah 现在我明白你的意思对不起,我能读到什么来实现我想要的或你知道的任何文档?
  • 改变 $app->add(new LocalitiesCommand());到 $app->add($container->get(LocalitiesCommand::class); 并公开你的服务可能会成功。但老实说,一旦你开始在这些事情中使用容器,然后只使用 symfony/skeleton app 更有意义。我还假设您只显示了 services.yaml 文件的一部分。您显然需要实际扫描目录或至少将您的命令添加为服务。

标签: php symfony dependency-injection symfony-console


【解决方案1】:

首先,让我们澄清一个误解:

但是当我实例化我的命令时,仍然要求我在构造函数中添加我的服务。为什么它不起作用?

如果您致电 new Foo(),那么您将不再获得自动接线 DI 的好处。如果你想使用自动装配和自动依赖注入,你需要让 Symfony 为你工作。当你调用new时,你是在手动实例化对象,你需要自己处理DI。

排除了这个问题,你将如何做到这一点?


首先,composer.json 带有基本依赖项和自动加载器声明:

完整的目录结构最终会是这样的:

<project_dir>
├── composer.json 
├── app 
├── src/
│    ├── ConsoleCommand/
│    │       └── FooCommand.php
│    └── Text/
│          └── Reverser.php
├── config/
│    ├── services.yaml

现在,每个部分:

包含所有依赖项和自动加载器的 composer.json 文件:

{
    "require": {
        "symfony/dependency-injection": "^5.3",
        "symfony/console": "^5.3",
        "symfony/config": "^5.3",
        "symfony/yaml": "^5.3"
    },
    "autoload": {
        "psr-4": {
            "App\\": "src"
        }
    }
}

前端控制器脚本,运行应用程序的文件(app,在我的例子中):

#!/usr/bin/env php
<?php declare(strict_types=1);

use Symfony\Component;

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

class App extends Component\Console\Application
{

    public function __construct(iterable $commands)
    {
        $commands = $commands instanceof Traversable ? iterator_to_array($commands) : $commands;

        foreach ($commands as $command) {
            $this->add($command);
        }

        parent::__construct();
    }
}

$container = new Component\DependencyInjection\ContainerBuilder();
$loader    = new Component\DependencyInjection\Loader\YamlFileLoader($container, new Component\Config\FileLocator(__DIR__ . '/config'));

$loader->load('services.yaml');
$container->compile();

$app = $container->get(App::class);
$app->run();

项目的服务容器配置:

# config/services.yaml
services:
  _defaults:
    autowire: true

  _instanceof:
    Symfony\Component\Console\Command\Command:
      tags: [ 'app.command' ]

  App\:
    resource: '../src/*'

  App:
    class: \App
    public: true
    arguments:
      - !tagged_iterator app.command

一个FooCommand类:

<?php declare(strict_types=1);

// src/ConsoleCommand/FooCommand.php

namespace App\ConsoleCommand;

use App\Text\Reverser;
use Symfony\Component\Console;

class FooCommand extends Console\Command\Command
{

    protected static $defaultName = 'foo';

    public function __construct(private Reverser $reverser)
    {
        parent::__construct(self::$defaultName);
    }

    protected function execute(Console\Input\InputInterface $input, Console\Output\OutputInterface $output): int
    {
        $output->writeln('Foo was invoked');
        $output->writeln($this->reverser->exec('the lazy fox'));

        return self::SUCCESS;
    }
}

以上依赖App\Text\Reverser服务,由DI组件自动为我们注入:

<?php declare(strict_types=1);

namespace App\Text;

class Reverser
{

    public function exec(string $in): string
    {
        return \strrev($in);
    }
}

安装和转储自动加载器后,通过执行php app (1) 我知道foo 命令可用 (2):

我可以执行php app foo,并且命令正确执行,使用它注入的依赖项:

一个独立的 Symfony 控制台应用程序,具有最少的依赖和自动依赖注入。

(一个非常相似的例子的所有代码,here)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-12-31
    • 2018-01-04
    • 1970-01-01
    • 2012-04-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多