Background
我想使用功能凝聚力来组织我的控制器。
这意味着我不会有Controllers/
目录,这使得框架变得容易,但我将按用例组织我的代码。举个任意例子:
src/FetchLatestNews/Controller.php
src/FetchLatestNews/News.php
src/FetchLatestNews/NewsRepository.php
- 。等
但是,Symfony默认设置为要求所有控制器在一个地方。请参阅services.yaml
:
# controllers are imported separately to make sure services can be injected
# as action arguments even if you don't extend any base controller class
AppController:
resource: '../src/Controller'
tags: ['controller.service_arguments']
按照上面的模板,我可以将新src/FetchLatestNews/Controller
放在那里,然后每次添加每个新控制器。但是,我不想,也不应该在每次创建新用例时手动更新此文件。
问题
如何避免此错误:
也许您忘记将控制器注册为服务或错过了使用"controller.service_arguments"标记它
作为用户,我不必关心手动添加service_arguments
或任何其他配置。当我指定路由时,它指向的东西因此是一个控制器。
理想的工作流程是:
- 在我喜欢的任何地方创建一个控制器。
- 添加引用控制器
routes.yaml
路由。 - 就是这样!
是否有可能在Symfony中实现此工作流程?它应该是,因为来自路由的任何内容 ->某个类都将是一个控制器。有没有办法使用正则表达式或其他解决方案?
注意:这个答案表明一个空的界面有点"黑客"。这也不是理想的。还有其他选择吗?
请注意,此答案已再次更新,增加了新信息。
仅当允许操作注入时才需要controller.service_arguments
标记。坚持使用构造函数注入和__invoke()
,这就是您所需要的。
下面的公共添加是一个要求,但事实证明你不需要编译器传递,它可以在 services.yaml 级别完成。因此,您唯一需要的是以下内容:
services:
_defaults:
autowire: true
autoconfigure: true
public: true // <---- This is the new additional config.
然后,任何类都可以在routes.yaml
中用作控制器,并且构造注入工作正常。
注意:原始答案如下。感谢Jakumi为我指出正确的方向。
您需要添加编译器传递才能执行两件事。在引导和注册自动依赖注入的内容时,如果类的名称中有Controller
:
- 添加标签
controller.service_arguments
,这是您通常需要手动添加自己的标签,services.yaml
允许二传手注入(如果您不关心二传手注入,可以忽略这一点( - 将类设置为公共,因为Symfony有一个奇怪的想法,即公共或私有的概念不是从类访问修饰符中推断出来的(认真的?( - 这是重要的事情,因为Symfony会抱怨控制器是私有的,否则控制器是私有的。
不要忘记,您不必使用"名称中的控制器"的逻辑。它可以是"控制器"在类名的末尾,这可能更好。
无论如何,首先创建一个CompilerPass
。把它放在任何你想要的地方。我把我的放在Kernel.php
旁边.
use SymfonyComponentDependencyInjection{CompilerCompilerPassInterface, ContainerBuilder};
class ControllersAsServices implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
foreach ($container->getDefinitions() as $definition) {
if (strpos($definition->getClass(), "Controller") === false) {
continue;
}
$definition->addTag("controller.service_arguments");
$definition->setPublic(true);
}
}
}
然后在你的Kernel.php
,你需要告诉它使用这个新的。覆盖受保护的函数build
并添加编译器传递,如文档所示:
protected function build(ContainerBuilder $container)
{
$container->addCompilerPass(new ControllersAsServices, PassConfig::TYPE_BEFORE_OPTIMIZATION, -1);
parent::build($container);
}
清除开发缓存。在我这样做之前,对我来说没有任何改变。
现在,名称中带有Controller
的任何类都可以像最初一样自动连线。
来自Jakumi的原始安瑟尔如下:
我相信在您的情况下,最好的方法是实现 CompilerPass,将类添加到容器中:
https://symfony.com/doc/current/bundles/extension.html#adding-classes-to-compile
在此过程中,可能还有一种方法可以添加标签。
Symfony通过RegisterControllerArgumentLocatorsPass迎合controller.service_arguments标签,它解析构造函数参数并检查某些特征。如果你的编译器通行证的优先级更高,你可能可以很容易地解决这个问题,然后......