Middleware

Middleware — PSR-15 шар між маршрутизацією та handler: фільтрує запит, додає контекст, перехоплює винятки або змінює відповідь. Concept Core використовує Psr\Http\Server\MiddlewareInterface і League Route lazyMiddleware().

Middleware — не те саме, що route interceptors: interceptors спрацьовують у RouteStrategy після middleware, до виклику handler, і не формують PSR-15 ланцюжок.

Див. також: життєвий цикл → middleware, реєстрація на маршрутах.

Контракт PSR-15

<?php declare(strict_types=1);

namespace Concept\App\Http\Middlewares;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;

final class ExampleMiddleware implements MiddlewareInterface
{
    public function process(
        ServerRequestInterface $request,
        RequestHandlerInterface $handler
    ): ResponseInterface {
        // До handler: перевірки, зміна $request

        $response = $handler->handle($request);

        // Після handler: заголовки, логування

        return $response;
    }
}

Клас реєструється в контейнері; League Route створює екземпляр через DI при lazyMiddleware(MyMiddleware::class) — залежності конструктора підставляються автоматично (ReflectionContainer).

Де підключати

  • Глобально — на початку routes/web.php (усі маршрути файлу).
  • Група$router->group(...)->lazyMiddleware(...).
  • Один маршрут — ланцюжок після get() / post().
  • Компонент — у Component/routes.php (наприклад AuthMiddleware на групі /admin).

Порядок реєстрації визначає порядок обгортання: перший доданий middleware — зовнішній шар (виконується першим на вході). Мінімальний skeleton не підключає глобальний стек;

Middleware ядра

Класи в Concept\Core\Http\Middlewares:

Клас Призначення
HandleValidationExceptionMiddleware Ловить ValidationException з FormRequest: JSON 422 (RequestFormat::expectsJson()) або flash + ResponseFactory::back()
VerifyCsrfTokenMiddleware CSRF для POST, PUT, PATCH, DELETE; при помилці — CsrfException
StorePreviousUrlMiddleware На GET (не AJAX) зберігає _url_current / _url_previous у сесії; задає Concept\Core\Http\Requests\RequestAttribute::SAFE_BACK_URL
ShareViewDataMiddleware Flash, помилки валідації, old input, csrf_tokenConcept\Core\Http\Requests\RequestAttribute::VIEW_PAYLOAD
ParseJsonBodyMiddleware Якщо Content-Type містить json — парсить body у getParsedBody()
ForceJsonResponseMiddleware Встановлює Accept: application/json для внутрішньої логіки; у відповіді — Content-Type: application/json

Типовий глобальний стек

$router->lazyMiddleware(HandleValidationExceptionMiddleware::class);
$router->lazyMiddleware(VerifyCsrfTokenMiddleware::class);
$router->lazyMiddleware(StorePreviousUrlMiddleware::class);
$router->lazyMiddleware(ShareViewDataMiddleware::class);
  1. Зовнішній try/catch валідації (обгортає весь ланцюжок)
  2. Перевірка CSRF до зміни стану
  3. Фіксація URL перед рендером
  4. Підготовка shared-даних для Twig (ViewResponseFactory)

CSRF

Токен зберігається в сесії під ключем SessionKey::CSRF_TOKEN (_csrf_token) через CsrfTokenManager. У формах — поле _csrf_token; для AJAX — заголовки X-CSRF-TOKEN або X-XSRF-TOKEN (константи HttpHeader).

<input type="hidden" name="_csrf_token" value="{{ csrf_token }}">

У Twig змінна csrf_token (ViewKey::CSRF_TOKEN) потрапляє через ShareViewDataMiddlewareViewResponseFactory::create(). Детальніше — Захист CSRF.

Middleware авторизації

У застосунку — власні класи на групах маршрутів:

  • AuthMiddleware (AuthAdmin) — сесія, роль admin, статус ACTIVE; redirect на /admin/login
  • ActiveUserMiddleware (UserCabinet) — авторизація, статуси pending/blocked; redirectByName('cabinet.login')
$router->group('/admin', function (RouteGroup $route) {
    // ...
})->lazyMiddleware(AuthMiddleware::class);

$router->group('/cabinet', function (RouteGroup $route) {
    // ...
})->lazyMiddleware(ActiveUserMiddleware::class);

При відмові контролер не виконується — middleware повертає redirect-відповідь.

Атрибути запиту

Middleware передає дані далі через $request->withAttribute():

КонстантаХто задаєХто читає
Concept\Core\Http\Requests\RequestAttribute::VIEW_PAYLOAD ShareViewDataMiddleware ViewResponseFactoryViewInterface::share()
Concept\Core\Http\Requests\RequestAttribute::SAFE_BACK_URL StorePreviousUrlMiddleware ResponseFactory::back()

Ключі всередині VIEW_PAYLOAD (ViewKey): errors, old, flashes, csrf_token.

ResponseFactory::back() спочатку перевіряє заголовок Referer, потім SAFE_BACK_URL, і лише внутрішні URL (див. безпечні редіректи).

Власний middleware

  1. Створіть клас з MiddlewareInterface (наприклад у src/App/Middlewares/ або в компоненті).
  2. Залежності — через конструктор (контейнер підставить автоматично).
  3. Підключіть lazyMiddleware() глобально, на групу або маршрут.
  4. Перевірте порядок відносно CSRF, auth і HandleValidationExceptionMiddleware.

Middleware має бути «вузьким»: одна відповідальність. Складну бізнес-логіку краще в сервісі або контролері.

Перегляд middleware

php bin/console.php route:list
php bin/console.php route:list --full-middleware
php bin/console.php route:list -F

Колонка middleware показує глобальні та локальні класи для кожного маршруту.