Middleware
Middleware — это код, который выполняется до и после контроллера:
аутентификация, проверка прав, логирование, тайминги. В Winter middleware — это
класс, который одновременно является PHP-атрибутом, поэтому применяется
декларативно: #[AuthMiddleware] над контроллером или методом.
Что такое middleware и зачем
Middleware (промежуточный слой) — код, который оборачивает обработку запроса: выполняется до контроллера и после него.
Проблема. Многие задачи не относятся к бизнес-логике конкретного эндпоинта, но нужны сразу многим: проверить токен, убедиться в правах, залогировать запрос, замерить время. Если писать это в каждом контроллере — получаются копипаст и дублирование сквозной логики. Решение — вынести такую обёртку в отдельный переиспользуемый слой и навешивать его декларативно там, где нужно.
В Winter Middleware — стереотип с двумя хуками:
before(HttpRequest, HttpResponse)— до контроллера. Здесь проверяют токен, права, лимиты. Бросьте исключение, чтобы прервать запрос.after(mixed $result): mixed— после контроллера. Может преобразовать или заменить возвращённое значение.
namespace Flytachi\Winter\K2\Stereotype;
abstract class Middleware implements MiddlewareInterface
{
public function before(HttpRequest $request, HttpResponse $response): void {}
public function after(mixed $result): mixed { return $result; }
}Переопределяйте только нужный хук — второй остаётся no-op’ом.
Создание
Сгенерируйте middleware командой make (суффикс Middleware добавится сам):
php call make -m .Auth # → main/AuthMiddleware.phpКлючевой момент в каркасе — объявление класса как атрибута. Без строки
#[\Attribute(...)] его нельзя будет повесить на контроллер:
<?php
namespace Main;
use Flytachi\Winter\K2\Http\Contracts\HttpRequest;
use Flytachi\Winter\K2\Http\Contracts\HttpResponse;
use Flytachi\Winter\K2\Stereotype\Middleware;
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD)]
class AuthMiddleware extends Middleware
{
public function before(HttpRequest $request, HttpResponse $response): void
{
// логика проверки
}
}TARGET_CLASS | TARGET_METHOD разрешает применять middleware и к контроллеру
целиком, и к отдельному методу.
before() — прервать запрос
Чтобы отклонить запрос, бросьте MiddlewareException (по умолчанию 401) или
любое ResponseException. Фреймворк перехватит его и вернёт корректный
HTTP-ответ — контроллер не вызовется:
use Flytachi\Winter\Base\HttpCode;
use Flytachi\Winter\K2\Http\Middleware\MiddlewareException;
public function before(HttpRequest $request, HttpResponse $response): void
{
$token = $request->header()->getBearerToken();
if ($token === null) {
throw new MiddlewareException('Token missing'); // 401
}
if (! $this->isValid($token)) {
MiddlewareException::throw('Forbidden', HttpCode::FORBIDDEN); // 403
}
}Можно добавить заголовок к ответу об ошибке:
throw (new MiddlewareException('Rate limited', HttpCode::TOO_MANY_REQUESTS))
->withHeader('Retry-After', '60');after() — преобразовать ответ
after() получает результат обработчика и может его изменить — обернуть, добавить
метаданные, отфильтровать. Возвращённое значение идёт дальше по цепочке:
public function after(mixed $result): mixed
{
// например, единый конверт ответа
return $result;
}Зависимости и контекст запроса
Middleware создаётся контейнером — работают конструктор и #[Autowired]. Частый
приём: middleware аутентификации кладёт пользователя в request-scope контекст, а
контроллеры и сервисы читают его через тот же #[Autowired]:
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD)]
class AuthMiddleware extends Middleware
{
#[Autowired] private AuthContext $context; // #[Request]-scope
public function before(HttpRequest $request, HttpResponse $response): void
{
$user = $this->authenticate($request);
$this->context->setUser($user); // доступно всем в этом запросе
}
}Про request-scope и AuthContext — на странице
Внедрение зависимостей.
Применение и порядок
Вешайте middleware на контроллер (все методы) или на отдельный метод:
#[AuthMiddleware] // на весь контроллер
#[RequestMapping('admin')]
class AdminController extends Controller
{
#[GetMapping('public-info')]
public function info(): ResponseEntity { /* тоже под AuthMiddleware */ }
#[RateLimitMiddleware] // добавляется к этому методу
#[GetMapping('stats')]
public function stats(): ResponseEntity { /* Auth + RateLimit */ }
}Когда middleware несколько:
before()выполняются в порядке объявления (сверху вниз).after()выполняются в обратном порядке (как вложенные обёртки).
#[A] #[B] контроллер
A::before → B::before → [контроллер] → B::after → A::afterПод капотом
Где middleware встраивается в обработку запроса. Для повседневной работы знать не обязательно.
Middleware — это шаги 9 и 11 конвейера Router::handle():
8. Пер-маршрутный CORS
9. Middleware before() — в порядке объявления
10. Метод контроллера
11. Middleware after() — в обратном порядке
12. Сериализация ответа
13. Обработка ошибок — ExceptionWrapper ловит прерывания из before()- Прерывание = исключение.
before()не возвращает «стоп»-флаг — он бросаетMiddlewareException/ResponseException, а шаг 13 (ExceptionWrapper) превращает его в HTTP-ответ. Поэтому прервать запрос можно из любой глубины. - Middleware резолвится контейнером. Экземпляр строится через DI, так что
зависимости и request-scope доступны в
before()до контроллера. after()— обёртки наизнанку. Обратный порядок делает пары before/after симметричными, как вложенныеtry/finally.
Полный конвейер — в разделе «Под капотом» на странице Маршрутизации.
Дальше
- CORS — частный случай кросс-доменной политики
- Контроллеры — что middleware оборачивает
- Внедрение зависимостей — request-scope контекст