Symfony 4中如何将自定义配置数组注入FooCommand命令类?
问题原因分析
你遇到的报错是因为Symfony 4中命令类的自动注册时机晚于扩展的load方法执行时间。
具体来说,Symfony是通过AddConsoleCommandPass这个编译器Pass来自动注册命令类为服务的,这个Pass会在所有扩展的load方法执行完成之后才运行。所以当你在FooExtension::load()里尝试获取FooCommand的服务定义时,这个服务还没被注册到容器中,自然会提示“服务不存在”。
解决方案
下面提供几种可行的解决方式,你可以根据自己的需求选择:
方案1:使用编译器Pass注入配置(推荐,保留自动注册特性)
编译器Pass是在所有扩展加载完成后运行的,此时命令类已经被自动注册为服务,我们可以通过它来注入配置:
步骤1:创建编译器Pass类
// src/DependencyInjection/Compiler/InjectFooConfigPass.php namespace App\DependencyInjection\Compiler; use App\Command\FooCommand; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; class InjectFooConfigPass implements CompilerPassInterface { private array $fooConfig; public function __construct(array $fooConfig) { $this->fooConfig = $fooConfig; } public function process(ContainerBuilder $container): void { // 先检查服务是否存在,避免因自动注册被禁用导致的报错 if (!$container->hasDefinition(FooCommand::class)) { return; } $commandDefinition = $container->getDefinition(FooCommand::class); // 推荐用命名参数注入(Symfony 4.3+支持),避免参数顺序变化导致的问题 $commandDefinition->setNamedArgument('$config', $this->fooConfig); // 如果你的Symfony版本低于4.3,改用按索引注入($config是构造函数第二个参数,索引为1) // $commandDefinition->setArgument(1, $this->fooConfig); } }
步骤2:在扩展中注册编译器Pass
修改你的FooExtension,将处理好的配置传入编译器Pass并注册:
// src/DependencyInjection/FooExtension.php namespace App\DependencyInjection; use App\DependencyInjection\Compiler\InjectFooConfigPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\Config\FileLocator; use Symfony\Component\HttpKernel\DependencyInjection\Extension; class FooExtension extends Extension { public function load(array $configs, ContainerBuilder $container): void { $configuration = $this->getConfiguration($configs, $container); $config = $this->processConfiguration($configuration, $configs); // 注册编译器Pass,传入验证后的配置 $container->addCompilerPass(new InjectFooConfigPass($config)); } }
方案2:手动注册命令类服务
如果你不介意放弃自动注册,可以直接在services.yaml中手动注册命令类,这样在扩展的load方法中就能获取到服务定义:
步骤1:在services.yaml中注册服务
# config/services.yaml services: App\Command\FooCommand: arguments: $foo: '@App\Foo' # 注入第一个依赖 autoconfigure: true # 保持命令能被控制台识别
步骤2:保留扩展中的注入代码
此时你的FooExtension::load()中的原有代码就能正常工作了,因为服务已经提前注册:
$fooCmdDef = $container->getDefinition(FooCommand::class); $fooCmdDef->addArgument($config); // 或者用setNamedArgument更安全:$fooCmdDef->setNamedArgument('$config', $config);
方案3:将配置存储为容器参数(更符合Symfony DI习惯)
把验证后的配置存储为容器参数,然后通过自动注入或手动配置注入到命令类中:
步骤1:在扩展中设置容器参数
// FooExtension::load() $container->setParameter('foo.config', $config);
步骤2:在命令类中注入参数
可以通过注解或配置文件两种方式注入:
方式A:使用Autowire注解(推荐,无需手动配置服务)
在命令类的构造函数参数上添加#[Autowire]注解,指定参数来源:
// src/Command/FooCommand.php namespace App\Command; use App\Foo; use Symfony\Component\Console\Command\Command; use Symfony\Component\DependencyInjection\Attribute\Autowire; class FooCommand extends Command { private Foo $foo; private array $config; public function __construct(Foo $foo, #[Autowire('%foo.config%')] array $config) { parent::__construct(); // 必须调用父类构造函数! $this->foo = $foo; $this->config = $config; } // ... 命令逻辑 ... }
方式B:手动配置services.yaml
如果不想用注解,直接在services.yaml中指定参数:
services: App\Command\FooCommand: arguments: $foo: '@App\Foo' $config: '%foo.config%' autoconfigure: true
重要提醒
不管用哪种方案,命令类的构造函数必须调用父类的__construct()方法,否则Symfony的Command类无法正常初始化,会出现其他奇怪的错误。
内容的提问来源于stack exchange,提问作者super.t
相关产品推荐
相关产品推荐

