Symfony 4 不自动装配动态路由控制器

Posted

技术标签:

【中文标题】Symfony 4 不自动装配动态路由控制器【英文标题】:Symfony 4 not autowiring dynamic route controllers 【发布时间】:2019-01-21 20:39:47 【问题描述】:

Symfony 版本:4.1.3

我有一个应用程序,它通过路由加载器服务根据配置加载动态路由,但 Symfony 4 似乎没有自动装配动态路由控制器。

我使用的是标准 Symfony 4 应用程序 services.yaml 文件:

/config/services.yaml

parameters:
services:
   # default configuration for services in *this* file
  _defaults:
     autowire: true      # Automatically injects dependencies in your services.
     autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
     public: false       # Allows optimizing the container by removing unused services; this also means
                         # fetching services directly from the container via $container->get() won't work.
                         # The best practice is to be explicit about your dependencies anyway.

  # makes classes in src/ available to be used as services
  # this creates a service per class whose id is the fully-qualified class name
  Application\Web\:
    resource: '../src/*'
    exclude: '../src/Search/Model,Entity,Migrations,Tests,Kernel.php'

  # controllers are imported separately to make sure services can be injected
  # as action arguments even if you don't extend any base controller class
  Application\Web\Controller\:
    resource: '../src/Controller'
    tags: ['controller.service_arguments']

路由加载器: src/Component/RouteLoader.php

<?php

namespace Application\Web\Component;

use Application\Symfony\Bundle\ConfigBundle\ReaderInterface;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;

/**
 * Class RouteLoader
 * @package Application\Web\Component
 */
class RouteLoader

    /**
     * @var ReaderInterface
     */
    private $configurationReader;

    public function __construct(ReaderInterface $configurationReader)
    
        $this->configurationReader = $configurationReader;
    

    public function load(): RouteCollection
    
        $routes = new RouteCollection();
        foreach ($this->configurationReader->find('category_navigation') as $label => $configuration) 
            $slug = strtolower($label);
            $route = new Route(
                $configuration['url'],
                [
                    '_controller' => [$configuration['controller'], 'dispatch'],
                    'categories_slug' => $slug,
                    'category_label' => $label,
                    'page_title' => $configuration['page_title'] ?? null,
                    'page_description' => $configuration['page_description'] ?? null,
                ],
                [],
                [],
                '',
                [],
                ['GET']
            );
            $routes->add($slug . '.home', $route);
        

        return $routes;
    

控制器构造函数: src/Controller/Page.php

<?php

namespace Application\Web\Controller;

//.... other class code

public function __construct(
    ClientInterface $client,
    ReaderInterface $configurationReader,
    \Twig_Environment $twigEnvironment,
    ContentSearcher $contentSearcher,
    Environment $environment,
    TokenStorageInterface $tokenStorage,
    UrlGeneratorInterface $urlGenerator
)

当我尝试加载页面时,Symfony 发出以下错误:

Controller "\Application\Web\Controller\Page" has required constructor arguments and does not exist in the container. Did you forget to define such a service?

但是,当我直接在 config/routes.yaml 文件中定义路由时,控制器会自动装配样式,没有任何问题!

我的问题是:

这是 Symfony 自动装配功能的限制吗,即不支持动态路由控制器? 在定义使自动装配工作的路线时,我是否遗漏了什么? 我是否发现了潜在的错误?

有什么想法吗?

编辑:堆栈跟踪

InvalidArgumentException:
Controller "\Application\Web\Controller\Page" has required constructor arguments and does not exist in the container. Did you forget to define such a service?

  at vendor/symfony/http-kernel/Controller/ContainerControllerResolver.php:62
  at Symfony\Component\HttpKernel\Controller\ContainerControllerResolver->instantiateController('\\Application\\Web\\Controller\\Page')
 (vendor/symfony/framework-bundle/Controller/ControllerResolver.php:54)
  at Symfony\Bundle\FrameworkBundle\Controller\ControllerResolver->instantiateController('\\Application\\Web\\Controller\\Page')
 (vendor/symfony/http-kernel/Controller/ControllerResolver.php:49)
  at Symfony\Component\HttpKernel\Controller\ControllerResolver->getController(object(Request))
 (vendor/symfony/http-kernel/Controller/TraceableControllerResolver.php:38)
  at Symfony\Component\HttpKernel\Controller\TraceableControllerResolver->getController(object(Request))
 (vendor/symfony/http-kernel/HttpKernel.php:132)
  at Symfony\Component\HttpKernel\HttpKernel->handleRaw(object(Request), 1)
 (vendor/symfony/http-kernel/HttpKernel.php:66)
  at Symfony\Component\HttpKernel\HttpKernel->handle(object(Request), 1, true)
 (vendor/symfony/http-kernel/Kernel.php:188)
  at Symfony\Component\HttpKernel\Kernel->handle(object(Request))
 (public/index.php:37)


 ArgumentCountError:
Too few arguments to function Application\Web\Controller\Base::__construct(), 0 passed in /var/www/html/vendor/symfony/http-kernel/Controller/ControllerResolver.php on line 133 and exactly 7 expected

  at src/Controller/Base.php:55
  at Application\Web\Controller\Base->__construct()
 (vendor/symfony/http-kernel/Controller/ControllerResolver.php:133)
  at Symfony\Component\HttpKernel\Controller\ControllerResolver->instantiateController('\\Application\\Web\\Controller\\Page')
 (vendor/symfony/http-kernel/Controller/ContainerControllerResolver.php:55)
  at Symfony\Component\HttpKernel\Controller\ContainerControllerResolver->instantiateController('\\Application\\Web\\Controller\\Page')
 (vendor/symfony/framework-bundle/Controller/ControllerResolver.php:54)
  at Symfony\Bundle\FrameworkBundle\Controller\ControllerResolver->instantiateController('\\Application\\Web\\Controller\\Page')
 (vendor/symfony/http-kernel/Controller/ControllerResolver.php:49)
  at Symfony\Component\HttpKernel\Controller\ControllerResolver->getController(object(Request))
 (vendor/symfony/http-kernel/Controller/TraceableControllerResolver.php:38)
  at Symfony\Component\HttpKernel\Controller\TraceableControllerResolver->getController(object(Request))
 (vendor/symfony/http-kernel/HttpKernel.php:132)
  at Symfony\Component\HttpKernel\HttpKernel->handleRaw(object(Request), 1)
 (vendor/symfony/http-kernel/HttpKernel.php:66)
  at Symfony\Component\HttpKernel\HttpKernel->handle(object(Request), 1, true)
 (vendor/symfony/http-kernel/Kernel.php:188)
  at Symfony\Component\HttpKernel\Kernel->handle(object(Request))
 (public/index.php:37)

【问题讨论】:

加载路由时,$configuration['url'] 是否包含用于类名的前导斜杠? @xabbuh $configuration['url'] 包含一个 URL,例如 /health。你的意思是我的班级名称是否有前导斜杠?如果是这样,是的。 @xabbuh 你真是个天才!当我从动态指定的类名中删除前导斜杠时,它起作用了! :) 【参考方案1】:

回答您的问题:

    不,至少在这种情况下不会。 Route Loader 的唯一作用是构建路由集合,以便可以匹配请求。通过提供_controller 参数,您可以告诉SF 哪个控制器映射到该路由。依赖注入组件负责将控制器作为服务自动加载到容器中。但是,如果控制器到达ContainerControllerResolver 并从第 50-52 行传递,那肯定是因为您的控制器没有注册为服务(或者更准确地说,无论$class 的值是多少) ContainerControllerResolver::instantiateController() 在容器中不存在)。 我无法复制,因为我没有您的服务。但我最好的猜测是,将_controller 参数作为某种可调用数组传递可能不起作用。尝试将其作为Application/Web/Pages::instance 之类的字符串传递。 ContainerControllerResolver 可以使用它。 我有疑问,但很有可能。

能够看到堆栈跟踪将非常有帮助。

更新:

在符合您的类名的字符串中有双反斜杠。尝试重新格式化为:Application\Web\Controller\Page

如果您想在重构之前确定此修复,请运行 bin/console debug:container Page 并检查您的页面控制器作为服务是否存在。如果是这样,那么问题肯定是带有双反斜杠的 FQCN。

【讨论】:

感谢您的洞察力和提示。我已更新问题以包含生成的堆栈跟踪。 更新中的建议解决了这个问题,@xabbuh 也指出了这一点。查看了bin/console debug:container 的输出后,我现在明白了为什么找不到该服务,这基于 Symfony 存储引用的方式。 你好,当我想在控制台上调用 debug:container 时,我没有得到输出,而只是堆栈跟踪的错误。

以上是关于Symfony 4 不自动装配动态路由控制器的主要内容,如果未能解决你的问题,请参考以下文章

Symfony 3.4 自动装配服务

无法自动装配服务:参数引用类但不存在此类服务

Hwi oauth bundle 和 Symfony 3.4 无法自动装配服务:如何在 symfony 3.4 + FOSUserBundle 中使用 hwi/oauth-bundle

Symfony 自动装配独白通道

使用 Symfony 自动装配时是不是可以排除多个目录?

8 -- 深入使用Spring -- 7...4 使用自动装配