Основы веб-разработки

Middleware

Middleware — это код, который выполняется до и после контроллера: аутентификация, проверка прав, логирование, тайминги. В Winter middleware — это класс, который одновременно является PHP-атрибутом, поэтому применяется декларативно: #[AuthMiddleware] над контроллером или методом.

База Stereotype\MiddlewareХуки before() / after()Применение атрибут

Что такое middleware и зачем

Middleware (промежуточный слой) — код, который оборачивает обработку запроса: выполняется до контроллера и после него.

Проблема. Многие задачи не относятся к бизнес-логике конкретного эндпоинта, но нужны сразу многим: проверить токен, убедиться в правах, залогировать запрос, замерить время. Если писать это в каждом контроллере — получаются копипаст и дублирование сквозной логики. Решение — вынести такую обёртку в отдельный переиспользуемый слой и навешивать его декларативно там, где нужно.

В Winter Middleware — стереотип с двумя хуками:

  • before(HttpRequest, HttpResponse) — до контроллера. Здесь проверяют токен, права, лимиты. Бросьте исключение, чтобы прервать запрос.
  • after(mixed $result): mixed — после контроллера. Может преобразовать или заменить возвращённое значение.
php
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 добавится сам):

bash
php call make -m .Auth   # → main/AuthMiddleware.php

Ключевой момент в каркасе — объявление класса как атрибута. Без строки #[\Attribute(...)] его нельзя будет повесить на контроллер:

main/AuthMiddleware.php
<?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-ответ — контроллер не вызовется:

php
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
  }
}

Можно добавить заголовок к ответу об ошибке:

php
throw (new MiddlewareException('Rate limited', HttpCode::TOO_MANY_REQUESTS))
  ->withHeader('Retry-After', '60');

after() — преобразовать ответ

after() получает результат обработчика и может его изменить — обернуть, добавить метаданные, отфильтровать. Возвращённое значение идёт дальше по цепочке:

php
public function after(mixed $result): mixed
{
  // например, единый конверт ответа
  return $result;
}

Зависимости и контекст запроса

Middleware создаётся контейнером — работают конструктор и #[Autowired]. Частый приём: middleware аутентификации кладёт пользователя в request-scope контекст, а контроллеры и сервисы читают его через тот же #[Autowired]:

php
#[\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 на контроллер (все методы) или на отдельный метод:

php
#[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() выполняются в обратном порядке (как вложенные обёртки).
text
#[A]  #[B]  контроллер
A::before → B::before → [контроллер] → B::after → A::after

Под капотом

Где middleware встраивается в обработку запроса. Для повседневной работы знать не обязательно.

Middleware — это шаги 9 и 11 конвейера Router::handle():

text
 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.

Полный конвейер — в разделе «Под капотом» на странице Маршрутизации.

Дальше