Приложение, которое я создаю, не будет работать традиционным способом. Все маршруты будут храниться в базе данных. И на основе предоставленного маршрута мне нужно получить правильный контроллер и действие для выполнения.
Насколько я понимаю, этого можно добиться с помощью прослушивателя событий "kernel.controller": https://symfony.com/doc/current/reference/events.html#kernel-контроллер
Я пытаюсь использовать предоставленную документацию, но приведенный здесь пример точно не показывает, как настроить новый вызываемый контроллер для передачи. И у меня здесь проблема, потому что я не знаю, как внедрить сервисный контейнер в мой только что вызванный контроллер.
Сначала установка:
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() для null
на линии: поставщик\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);
}
Контроллер настолько прост, насколько это возможно:
Домашняя страницаController.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()
, но что мне передать?
@yivi может быть, посмотрю. Прямо сейчас, основываясь на вопросе «AymDev», ответ сработал для меня.
Не внедряйте контейнер, контроллеры также являются службами, и их создание вручную не позволяет вам использовать внедрение зависимостей конструктора. Используйте локатор сервисов, который содержит только контроллеры:
Объявлено в конфиг/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;
}
}
# ...
}
}
Если вы сбросите любой контроллер, вы увидите, что контейнер установлен. То же самое касается дополнительной услуги, которую вы автоматически подключаете из конструктора.
Создание загрузчик пользовательских маршрутов может быть лучшей идеей, чем эта.