如何在带有命令的独立应用程序中使用 symfony 中的 DependencyInjection?
How to use DependencyInjection from symfony in stand alone application with commands?
我一直在使用 symfony/console 来制作命令并像那样注册它们,一切正常:
bin/console:
#!/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
但是当我实例化我的命令时仍然要求我在构造函数中添加我的服务。为什么它不起作用?
首先,让我们澄清一个误解:
But still asks me to add my service in the constructor when I instantiate my command. Why is it not working?
如果您调用 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
class:
<?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)。
我一直在使用 symfony/console 来制作命令并像那样注册它们,一切正常:
bin/console:
#!/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
但是当我实例化我的命令时仍然要求我在构造函数中添加我的服务。为什么它不起作用?
首先,让我们澄清一个误解:
But still asks me to add my service in the constructor when I instantiate my command. Why is it not working?
如果您调用 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
class:
<?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)。