背景
我想使用functional cohesion组织我的 Controller 。
这意味着我不会有 Controllers/
目录,这使得框架变得容易,但我将按用例组织我的代码。任意示例:
src/FetchLatestNews/Controller.php
src/FetchLatestNews/News.php
src/FetchLatestNews/NewsRepository.php
- ...等等
但是,Symfony 默认设置为要求所有 Controller 位于一处。请参阅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
放置在那里,然后每次添加每个新 Controller 。但是,我不想、也不应该在每次创建新用例时手动更新此文件。
问题
如何避免此错误:
maybe you forgot to register the controller as a service or missed tagging it with the "controller.service_arguments"
作为用户,我不必关心手动添加 service_arguments
或任何其他配置。当我指定一条路线时,它所指向的东西就是一个 Controller 。
理想的工作流程是:
- 在我喜欢的任何地方创建一个 Controller 。
- 将路由添加到引用 Controller 的
routes.yaml
中。 - 就是这样!
是否可以在 Symfony 中实现这个工作流程?应该是这样,因为路由中的任何东西 --> 某个类都将是 Controller 。有没有办法使用正则表达式或其他解决方案?
注意:this answer建议使用一种空接口(interface)“hack”。这也不理想。还有其他选择吗?
最佳答案
Please note, this answer has been updated again with new information.
仅在允许操作注入(inject)时才需要 controller.service_arguments
标记。坚持使用构造函数注入(inject)和 __invoke()
这就是您所需要的。
下面的公共(public)添加是一个要求,但事实证明您不需要为此提供编译器通行证,并且这一切都可以在 services.yaml 级别完成。因此,您唯一需要的是以下内容:
services:
_defaults:
autowire: true
autoconfigure: true
public: true // <---- This is the new additional config.
然后,任何类都可以在 routes.yaml
中用作 Controller ,并且构造注入(inject)工作得很好。
Note: The original answer is below. Thanks to Jakumi for pointing me in the right direction.
您需要添加编译器 channel 来完成两件事。在启动和注册自动依赖项注入(inject)时,如果类的名称中包含 Controller
:
- 添加标记
controller.service_arguments
,这是您通常需要在services.yaml
中手动添加的标记,以允许 setter 注入(inject)(您可以忽略此标记)如果你不关心 setter 注入(inject)) - 将类设置为 public,因为 Symfony 有一个奇怪的想法,即公共(public)或私有(private)任何东西的概念都不能从类访问修饰符中推断出来(认真的吗?) - 这是 Symfony 会提示的重要事情否则 Controller 是私有(private)的
不要忘记您不必使用“名称中的 Controller ”的逻辑。可以将“Controller”放在类名的末尾,这可能会更好。
无论如何,首先创建一个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
中,您需要告诉它使用这个新的。覆盖 protected 函数 build
并添加编译器传递,如文档所示:
protected function build(ContainerBuilder $container)
{
$container->addCompilerPass(new ControllersAsServices, PassConfig::TYPE_BEFORE_OPTIMIZATION, -1);
parent::build($container);
}
清除您的开发缓存。在我这样做之前,一切都没有改变。
现在,任何名称中带有 Controller
的类都可以像原来那样 Autowiring 。
Jakumi 的原始答案如下:
我相信您的情况最好的方法是实现 CompilerPass,将类添加到容器中:
https://symfony.com/doc/current/bundles/extension.html#adding-classes-to-compile
在此过程中,可能还有一种方法可以添加标签。
Symfony 通过RegisterControllerArgumentLocatorsPass 来满足controller.service_arguments 标签的需求。 ,它解析构造函数参数并检查一些特征。如果你的编译器通行证的优先级更高,你可能可以轻松解决这个问题......
关于php - 如何在 Symfony 5 的其他目录中自动注册 'controllers as services',我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62115738/