Во многих фреймворках функциональность middlewere реализована из коробки, в Symfony такого нет, но фреймворк насколько гибок, что позволяет с легкостью добавить этот функционал.
Создадим интерфейс MiddlewareInterface, будем реализовывать его в контроллерах, в которые мы хотим внедрить middleware:
use Symfony\Component\HttpFoundation\Response;
interface MiddlewareInterface
{
public function before();
public function after(Response $response);
}
Создадим контроллер, который реализует этот интерфейс:
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class PageController extends AbstractController implements MiddlewareInterface {
protected string $title = 'Заголовок по умолчанию';
public function before()
{
$this->title = 'Заголовок, установленный в before';
}
#[Route('/', name: 'app_demo_page')]
public function index(): Response
{
return $this->render('demo_page/index.html.twig', [
'controller_name' => $this->title,
]);
}
public function after(Response $response): Response
{
$responseContent = $response->getContent();
$responseContent = str_replace('color: red;', 'color: blue;', $responseContent);
$response->setContent($responseContent);
return $response;
}
}
В методе before изменим заголовок нашей страницы. В шаблоне он стилизован красным цветом через инлайн стиль:
...
<h1 style="color: red;">{{ controller_name }}</h1>
...
Теперь, если мы перейдем на наш сайт, вы увидим на странице "Заголовок по умолчанию", выделенный красным цветом.
Добавим слушатель событий для запуска наших before/after. Before мы должны запустить после создания контроллера, но перед вызовом экшена. В Symfony для этого подходят события kernel.controller и kernel.controller_arguments. Первое создается после создания контроллера, второе, непосредственно перед вызовом экшена, после разрешения аргументов.
Для метода after удобно использовать событие kernel.response. Оно происходит сразу после выполнения экшена.
Создадим слушатель этих событий:
use App\Controller\MiddlewareInterface;
use Symfony\Component\HttpKernel\Event\ControllerEvent;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
class Middleware
{
public null | MiddlewareInterface $currentController = null;
public function onKernelController(ControllerEvent $event)
{
$controller = $event->getController();
if (is_array($controller))
{
$controller = $controller[0];
}
if ($controller instanceof MiddlewareInterface)
{
$this->currentController = $controller;
$this->currentController->before();
}
}
public function onKernelResponse(ResponseEvent $event)
{
if ($this->currentController instanceof MiddlewareInterface)
$event->setResponse($this->currentController->after($event->getResponse()));
}
}
В функции onKernelController (слушатель события kerenl.controller) мы проверим, реализует ли наш контроллер интерфейс MiddlewareInterface, сохраним ссылку на наш контроллер и вызовем метод before(). Во второй функции onKernelResponse (слушатель события kerenl.response) мы вызовем наш метод after, передав туда объект Response, который мы получили из экшена. After вернет обработанный Response, и его мы установим в качестве ответа.
Для того, чтобы наш слушатель подхватился, нам нужно объявить его, как сервис. Для этого добавим в config/servises.yaml соответствующий код:
services:
...
App\EventListener\Middleware:
tags:
- { name: kernel.event_listener, event: kernel.controller }
- { name: kernel.event_listener, event: kernel.response }
...
Запустив теперь наше приложение, мы увидим, что заголовок "Заголовок, установленный в before" стал синим (был изменен в after).
Приведенные выше примеры скорее для наглядности, и не несут большой смысловой нагрузки. На самом деле в before/after можно размещать какую угодно логику, проверять уровень доступа пользователя, работать с конфигурацией, внедрять дополнительные модули на страницу, реализовывать защиту парсеров данных и многое-многое другое.
Приведенный способ - не единственный. Наряду со солушателем событий можно использовать подсписчиков, но об этом как-нибудь в другой раз.