如何在Symfony 5的其他目录中自动注册'controllers as services'



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标签,它解析构造函数参数并检查某些特征。如果你的编译器通行证的优先级更高,你可能可以很容易地解决这个问题,然后......