简体   繁体   中英

Symfony 4 not autowiring dynamic route controllers

Symfony version: 4.1.3

I have an application which loads dynamic routes based on configuration by virtue of a route loader service, but it appears that Symfony 4 is not autowiring the dynamic route controllers.

I am using the standard Symfony 4 application services.yaml file:

/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']

Route Loader: 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;
    }
}

Controller constructor: 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
)

When I try to load the page, Symfony whines with the following error:

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

However, when I define the route directly in the config/routes.yaml file, the controller is autowired in style with no issues!

My questions are:

  • Is this a limitation of Symfony's autowiring capability, ie not supported for dynamic route controllers?
  • Is there something that I'm missing when it comes to defining the routes that will make the autowiring work?
  • Have I potentially identified a bug?

Any ideas?

EDIT: Stack traces

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)

Answering your questions:

  1. No, at least not in this case. The only role of the Route Loader is to build a collection of routes so a request can be matched against it. By providing the _controller parameter, you are telling SF which controller is mapped to that route. The Dependency Injection component is the one in charge of autoloading the controllers into the container as a service. But if the controller reaches the ContainerControllerResolver and passes from lines 50-52, it is definitely due to the fact that your controller is not registered as a service (or more precisely, to the fact that whatever is the value of $class in ContainerControllerResolver::instantiateController() does not exist in the container).
  2. I cannot reproduce, since I don't have your services. But my best guess is that is probably not working passing the _controller argument as some sort of callable array. Try pass it as a string like Application/Web/Pages::instance . The ContainerControllerResolver is able to work with that.
  3. I have my doubts, but it's probable.

It would be very helpful to be able to see an stack trace.

UPDATE:

You have double backslashes in the string that conforms your class name. Try reformatting to: Application\\Web\\Controller\\Page .

If you want to be sure of this fix before refactoring, run bin/console debug:container Page and check if your Page controller, as a service, exists. If it does, then the problem is, most certainly, the FQCN with double backslashes.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM