Symfony6 使用 "kernel.controller" 事件手动更改控制器。如何注入服务容器?
Symfony6 changing the controller manually using the "kernel.controller" event. How to inject the service container?
我正在构建的应用程序无法以传统方式运行。所有路线都将存储在数据库中。根据提供的路线,我需要获得正确的控制器和要执行的操作。
据我所知,这可以使用“kernel.controller”事件侦听器来实现:https://symfony.com/doc/current/reference/events.html#kernel-controller
我正在尝试使用提供的文档,但此处的示例并未准确显示如何设置要传递的新可调用控制器。我在这里遇到了一个问题,因为我不知道如何将服务容器注入到我新调用的控制器中。
首先设置:
services.yaml
parameters:
db_i18n.entity: App\Entity\Translation
developer: '%env(DEVELOPER)%'
category_directory: '%kernel.project_dir%/public/uploads/category'
temp_directory: '%kernel.project_dir%/public/uploads/temp'
product_directory: '%kernel.project_dir%/public/uploads/product'
app.supported_locales: 'lt|en|ru'
services:
_defaults:
autowire: true
autoconfigure: true
App\:
resource: '../src/'
exclude:
- '../src/DependencyInjection/'
- '../src/Entity/'
- '../src/Kernel.php'
App\Translation\DbLoader:
tags:
- { name: translation.loader, alias: db }
App\Extension\TwigExtension:
arguments:
- '@service_container'
tags:
- { name: twig.extension }
App\EventListener\RequestListener:
tags:
- { name: kernel.event_listener, event: kernel.controller, method: onControllerRequest }
听众:
RequestListener.php
<?php
namespace App\EventListener;
use App\Controller\Shop\HomepageController;
use App\Entity\SeoUrl;
use Doctrine\Persistence\ManagerRegistry;
use Exception;
use Psr\Container\ContainerInterface;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel\Event\ControllerEvent;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Security;
class RequestListener
{
public ManagerRegistry $doctrine;
public RequestStack $requestStack;
public function __construct(ManagerRegistry $doctrine, RequestStack $requestStack)
{
$this->doctrine = $doctrine;
$this->requestStack = $requestStack;
}
/**
* @throws Exception
*/
public function onControllerRequest(ControllerEvent $event)
{
if (!$event->isMainRequest()) {
return;
}
if(str_contains($this->requestStack->getMainRequest()->getPathInfo(), '/admin')) {
return;
}
$em = $this->doctrine->getManager();
$pathInfo = $this->requestStack->getMainRequest()->getPathInfo();
;
$route = $em->getRepository(SeoUrl::class)->findOneBy(['keyword' => $pathInfo]);
if($route instanceof SeoUrl) {
switch ($route->getController()) {
case 'homepage':
$controller = new HomepageController();
$event->setController([$controller, $route->getAction()]);
break;
default:
break;
}
} else {
throw new Exception('Route not found');
}
}
}
所以这是最基本的例子。我从数据库获取路由,如果它是“主页”路由,我创建新的 HomepageController 并设置操作。但是我缺少我不知道如何注入的容器接口。我收到此错误:
调用成员函数 has() on null
在线:vendor\symfony\framework-bundle\Controller\AbstractController.php:216
即:
/**
* Returns a rendered view.
*/
protected function renderView(string $view, array $parameters = []): string
{
if (!$this->container->has('twig')) { // here
throw new \LogicException('You cannot use the "renderView" method if the Twig Bundle is not available. Try running "composer require symfony/twig-bundle".');
}
return $this->container->get('twig')->render($view, $parameters);
}
控制器是最基本的:
HomepageController.php
<?php
namespace App\Controller\Shop;
use App\Repository\CategoryRepository;
use App\Repository\Shop\ProductRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class HomepageController extends AbstractController
{
#[Route('/', name: 'index', methods: ['GET'])]
public function index(): Response
{
return $this->render('shop/index.html.twig', [
]);
}
}
所以基本上容器没有设置。如果我转储 $event->getController()
我得到这个:
RequestListener.php on line 58:
array:2 [▼
0 => App\Controller\Shop\HomepageController {#417 ▼
#container: null
}
1 => "index"
]
我需要通过 $controller->setContainer()
来设置容器,但是我要传递什么?
不要注入容器,控制器也是服务,手动实例化它们会阻止您使用构造函数依赖注入。使用仅包含控制器的服务定位器:
在config/services.yaml中声明:
# config/services.yaml
services:
App\EventListener\RequestListener:
arguments:
$serviceLocator: !tagged_locator { tag: 'controller.service_arguments' }
然后在事件侦听器中,添加服务定位器参数并从中获取完全配置的控制器:
# ...
use App\Controller\Shop\HomepageController;
use Symfony\Component\DependencyInjection\ServiceLocator;
class RequestListener
{
# ...
private ServiceLocator $serviceLocator;
public function __construct(
# ...
ServiceLocator $serviceLocator
) {
# ...
$this->serviceLocator = $serviceLocator;
}
public function onControllerRequest(ControllerEvent $event)
{
# ...
if($route instanceof SeoUrl) {
switch ($route->getController()) {
case 'homepage':
$controller = $this->serviceLocator->get(HomepageController::class);
# ...
break;
default:
break;
}
}
# ...
}
}
如果转储任何控制器,您将看到容器已设置。同样适用于您从构造函数自动装配的附加服务。
我正在构建的应用程序无法以传统方式运行。所有路线都将存储在数据库中。根据提供的路线,我需要获得正确的控制器和要执行的操作。
据我所知,这可以使用“kernel.controller”事件侦听器来实现:https://symfony.com/doc/current/reference/events.html#kernel-controller
我正在尝试使用提供的文档,但此处的示例并未准确显示如何设置要传递的新可调用控制器。我在这里遇到了一个问题,因为我不知道如何将服务容器注入到我新调用的控制器中。
首先设置:
services.yaml
parameters:
db_i18n.entity: App\Entity\Translation
developer: '%env(DEVELOPER)%'
category_directory: '%kernel.project_dir%/public/uploads/category'
temp_directory: '%kernel.project_dir%/public/uploads/temp'
product_directory: '%kernel.project_dir%/public/uploads/product'
app.supported_locales: 'lt|en|ru'
services:
_defaults:
autowire: true
autoconfigure: true
App\:
resource: '../src/'
exclude:
- '../src/DependencyInjection/'
- '../src/Entity/'
- '../src/Kernel.php'
App\Translation\DbLoader:
tags:
- { name: translation.loader, alias: db }
App\Extension\TwigExtension:
arguments:
- '@service_container'
tags:
- { name: twig.extension }
App\EventListener\RequestListener:
tags:
- { name: kernel.event_listener, event: kernel.controller, method: onControllerRequest }
听众:
RequestListener.php
<?php
namespace App\EventListener;
use App\Controller\Shop\HomepageController;
use App\Entity\SeoUrl;
use Doctrine\Persistence\ManagerRegistry;
use Exception;
use Psr\Container\ContainerInterface;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel\Event\ControllerEvent;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Security;
class RequestListener
{
public ManagerRegistry $doctrine;
public RequestStack $requestStack;
public function __construct(ManagerRegistry $doctrine, RequestStack $requestStack)
{
$this->doctrine = $doctrine;
$this->requestStack = $requestStack;
}
/**
* @throws Exception
*/
public function onControllerRequest(ControllerEvent $event)
{
if (!$event->isMainRequest()) {
return;
}
if(str_contains($this->requestStack->getMainRequest()->getPathInfo(), '/admin')) {
return;
}
$em = $this->doctrine->getManager();
$pathInfo = $this->requestStack->getMainRequest()->getPathInfo();
;
$route = $em->getRepository(SeoUrl::class)->findOneBy(['keyword' => $pathInfo]);
if($route instanceof SeoUrl) {
switch ($route->getController()) {
case 'homepage':
$controller = new HomepageController();
$event->setController([$controller, $route->getAction()]);
break;
default:
break;
}
} else {
throw new Exception('Route not found');
}
}
}
所以这是最基本的例子。我从数据库获取路由,如果它是“主页”路由,我创建新的 HomepageController 并设置操作。但是我缺少我不知道如何注入的容器接口。我收到此错误:
调用成员函数 has() on null
在线:vendor\symfony\framework-bundle\Controller\AbstractController.php:216
即:
/**
* Returns a rendered view.
*/
protected function renderView(string $view, array $parameters = []): string
{
if (!$this->container->has('twig')) { // here
throw new \LogicException('You cannot use the "renderView" method if the Twig Bundle is not available. Try running "composer require symfony/twig-bundle".');
}
return $this->container->get('twig')->render($view, $parameters);
}
控制器是最基本的:
HomepageController.php
<?php
namespace App\Controller\Shop;
use App\Repository\CategoryRepository;
use App\Repository\Shop\ProductRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class HomepageController extends AbstractController
{
#[Route('/', name: 'index', methods: ['GET'])]
public function index(): Response
{
return $this->render('shop/index.html.twig', [
]);
}
}
所以基本上容器没有设置。如果我转储 $event->getController()
我得到这个:
RequestListener.php on line 58:
array:2 [▼
0 => App\Controller\Shop\HomepageController {#417 ▼
#container: null
}
1 => "index"
]
我需要通过 $controller->setContainer()
来设置容器,但是我要传递什么?
不要注入容器,控制器也是服务,手动实例化它们会阻止您使用构造函数依赖注入。使用仅包含控制器的服务定位器:
在config/services.yaml中声明:
# config/services.yaml
services:
App\EventListener\RequestListener:
arguments:
$serviceLocator: !tagged_locator { tag: 'controller.service_arguments' }
然后在事件侦听器中,添加服务定位器参数并从中获取完全配置的控制器:
# ...
use App\Controller\Shop\HomepageController;
use Symfony\Component\DependencyInjection\ServiceLocator;
class RequestListener
{
# ...
private ServiceLocator $serviceLocator;
public function __construct(
# ...
ServiceLocator $serviceLocator
) {
# ...
$this->serviceLocator = $serviceLocator;
}
public function onControllerRequest(ControllerEvent $event)
{
# ...
if($route instanceof SeoUrl) {
switch ($route->getController()) {
case 'homepage':
$controller = $this->serviceLocator->get(HomepageController::class);
# ...
break;
default:
break;
}
}
# ...
}
}
如果转储任何控制器,您将看到容器已设置。同样适用于您从构造函数自动装配的附加服务。