Symfony6 меняет контроллер вручную с помощью события «kernel.controller». Как внедрить сервисный контейнер?

Приложение, которое я создаю, не будет работать традиционным способом. Все маршруты будут храниться в базе данных. И на основе предоставленного маршрута мне нужно получить правильный контроллер и действие для выполнения.

Насколько я понимаю, этого можно добиться с помощью прослушивателя событий "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 04.05.2022 16:47

@yivi может быть, посмотрю. Прямо сейчас, основываясь на вопросе «AymDev», ответ сработал для меня.

domskat 04.05.2022 18:21
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Symfony Station Communiqué - 7 июля 2023 г
Symfony Station Communiqué - 7 июля 2023 г
Это коммюнике первоначально появилось на Symfony Station .
Оживление вашего приложения Laravel: Понимание режима обслуживания
Оживление вашего приложения Laravel: Понимание режима обслуживания
Здравствуйте, разработчики! В сегодняшней статье мы рассмотрим важный аспект управления приложениями, который часто упускается из виду в суете...
Установка и настройка Nginx и PHP на Ubuntu-сервере
Установка и настройка Nginx и PHP на Ubuntu-сервере
В этот раз я сделаю руководство по установке и настройке nginx и php на Ubuntu OS.
Коллекции в Laravel более простым способом
Коллекции в Laravel более простым способом
Привет, читатели, сегодня мы узнаем о коллекциях. В Laravel коллекции - это способ манипулировать массивами и играть с массивами данных. Благодаря...
Как установить PHP на Mac
Как установить PHP на Mac
PHP - это популярный язык программирования, который используется для разработки веб-приложений. Если вы используете Mac и хотите разрабатывать...
2
2
41
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Не внедряйте контейнер, контроллеры также являются службами, и их создание вручную не позволяет вам использовать внедрение зависимостей конструктора. Используйте локатор сервисов, который содержит только контроллеры:

Объявлено в конфиг/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;
            }
        }

        # ...
    }
}

Если вы сбросите любой контроллер, вы увидите, что контейнер установлен. То же самое касается дополнительной услуги, которую вы автоматически подключаете из конструктора.

Другие вопросы по теме