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

How can I automatically register 'controllers as services' in other directories in Symfony 5

背景

我想使用 functional cohesion 来组织我的控制器。

这意味着我不会有一个 Controllers/ 目录,这使得框架很容易,但我会按用例组织我的代码。任意示例:

但是,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
    App\Controller\:
        resource: '../src/Controller'
        tags: ['controller.service_arguments']

按照上面的模板,我 可以 将新的 src/FetchLatestNews/Controller 放在那里,然后每次都添加每个新控制器。但是,我不想,也不应该在每次创建新用例时手动更新此文件

问题

如何避免此错误:

maybe you forgot to register the controller as a service or missed tagging it with the "controller.service_arguments"

作为用户,我不必关心手动添加 service_arguments 或任何其他配置。当我指定一条路线时,它指向的东西因此是一个控制器。

理想的工作流程是:

是否可以在 Symfony 中实现此工作流程?应该是,因为路线中的任何东西 --> some class 都将成为控制器。有没有办法使用正则表达式或其他解决方案?

注意 建议使用 'hack' 的空接口。这也不理想。还有其他选择吗?

Please note, this answer has been updated again with new information.

controller.service_arguments 标签只需要允许动作注入。坚持使用构造函数注入和 __invoke(),这就是您所需要的。

下面的 public 添加是一项要求,但事实证明您不需要编译器通过,它可以在 services.yaml 级别完成。因此,您唯一需要的是:

services:
    _defaults:
        autowire: true
        autoconfigure: true
        public: true         // <---- This is the new additional config.

然后,任何 class 都可以在 routes.yaml 中用作控制器,构造注入工作得很好。

Note: The original answer is below. Thanks to Jakumi for pointing me in the right direction.

您需要添加一个编译器传递来完成两件事。在启动和注册自动依赖注入的东西时,如果 class 名称中有 Controller

  • 添加标签 controller.service_arguments,这是您通常需要在 services.yaml 中手动添加的标签,以允许 setter 注入(如果您不这样做,可以忽略它)不关心 setter 注入)
  • 将 class 设置为 public,因为 Symfony 有一个奇怪的想法,即不能从 class 访问中推断出 public 或私有任何东西的概念修饰符(认真的?)- 这是重要的事情,否则 Symfony 会抱怨控制器是私有的

别忘了您不必使用 "Controller in name" 的逻辑。它可以是 "Controller" 在 class 名称的末尾,这可能更好。

不管怎样,先创建一个CompilerPass。把它放在任何你想要的地方。我把我的放在 Kernel.php.

旁边
use Symfony\Component\DependencyInjection\{Compiler\CompilerPassInterface, 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 的任何 class 都可以自动装配,因为它本来应该是这样的。

下面来自 Jakumi 的原始答案:


我相信在你的情况下最好的方法是实施 CompilerPass,将 classes 添加到容器中:

https://symfony.com/doc/current/bundles/extension.html#adding-classes-to-compile

在此过程中,可能还有一种方法可以添加标签。

Symfony 通过 RegisterControllerArgumentLocatorsPass 迎合 controller.service_arguments 标签,它解析构造函数参数并检查某些特征。如果你让你的编译器传递优先级更高,你可能会很容易地解决这个问题......