Middleware
Triangle Middleware собирает цепочку обработчиков между HTTP-запросом и контроллером. Каждый обработчик может проверить запрос, завершить его собственным ответом либо вызвать следующий обработчик и изменить полученный ответ.
Это контракт Triangle, а не PSR-15: метод принимает request и callable следующего шага, а не ServerRequestInterface и RequestHandlerInterface.
Установка
Заголовок раздела «Установка»composer require triangle/middlewareПакет объявляет PHP ^8.1, triangle/engine ^8.1 и triangle/router ^2.0. HTTP-цепочку исполняет Triangle Engine-HTTP; сам компонент хранит регистрации и собирает список callbacks.
Обработчик
Заголовок раздела «Обработчик»<?php
namespace app\middleware;
use Triangle\Middleware\MiddlewareInterface;
final class ResponseHeader implements MiddlewareInterface{ public function process($request, callable $handler) { $response = $handler($request); return $response->header('X-Application', 'example'); }}До вызова handler удобно выполнять проверки, после — добавлять заголовки или метрики. Для отказа верните HTTP Response, не вызывая handler. Контроль доступа выполняйте на сервере; наличие middleware-класса в проекте не означает его включения в цепочку.
Не сохраняйте request, текущего пользователя или ответ в свойствах долгоживущего экземпляра middleware. Engine получает экземпляры из контейнера; время их жизни определяется контейнером и worker, а не названием метода process.
Подключение к приложению
Заголовок раздела «Подключение к приложению»В middleware.php возвращается карта областей и списков классов:
return [ '@' => [app\middleware\ResponseHeader::class],];@ означает глобальный middleware. Пустая строка обозначает общий список текущего приложения/плагина; именованный ключ — отдельное приложение. Bootstrap дополнительно читает static.middleware для статических ресурсов в отдельную область static.
Load проверяет наличие метода process через method_exists, но не проверяет реализацию интерфейса, видимость и точную сигнатуру. Ошибка может проявиться уже при исполнении запроса; используйте MiddlewareInterface и проверяйте реальный HTTP-путь.
Маршрут и контроллер
Заголовок раздела «Маршрут и контроллер»Маршрут может добавлять middleware через свой RouteObject. Контроллер может иметь свойство middleware со списком классов, а также атрибуты Triangle Annotation Middleware на классе и методе. Bootstrap собирает эти источники при подготовке callback; это не вызов каждого middleware при чтении конфигурации.
Для конфигурационных списков порядок объявления сохраняется при входе в HTTP-цепочку, а после handler обработчики завершаются в обратном порядке. Global идёт снаружи app-global, затем app и middleware контроллера/маршрута. Внутренний список getMiddleware намеренно развёрнут: Engine оборачивает им конечный callback. Не вызывайте этот список вручную как обычный foreach — это изменит контракт исполнения.
Маршрутный список дополнительно разворачивается при сборке, поэтому его порядок нужно сверять отдельно от конфигурационного. Атрибуты и controller property участвуют в той же композиции; при нескольких источниках закрепите ожидаемый trace интеграционным тестом.
Ошибки и перезагрузка
Заголовок раздела «Ошибки и перезагрузка»Неправильный тип списка и отсутствующий process отклоняются при load. Повторный load дописывает registrations; он не заменяет предыдущую конфигурацию и может создать дубли. Start(null) ничего не загружает.
HTTP Engine перехватывает Throwable из process и переводит его через обработчик исключений приложения. Это не повтор операции: middleware с внешним побочным эффектом должен сам обеспечивать идемпотентность и понятную обработку отказа.
MiddlewareInterface
Заголовок раздела «MiddlewareInterface»Triangle\Middleware\MiddlewareInterface::process($request, callable $handler) не задаёт return type. HTTP-приложение ожидает результат, совместимый с его Response-цепочкой. Вызов handler обязателен только когда запрос должен пройти дальше.
У интерфейса два аргумента. Не переносите сюда пример Engine-WS с иным транспортным контрактом: совпадающее слово middleware не делает сигнатуры взаимозаменяемыми.
Bootstrap
Заголовок раздела «Bootstrap»| Метод | Назначение |
|---|---|
start(?localzet\Server $server = null): void | Загрузить middleware приложения, static и плагинов; при null ничего не делать |
load(array $list, string $plugin = ''): void | Добавить registrations по областям |
getMiddleware(string $plugin, string $app, string|array|Closure $controller, ?RouteObject $route, bool $withGlobal = true): array | Собрать callbacks для оборачивания конечного handler |
Load сохраняет пары [class, 'process'], не создавая экземпляры. HTTP Engine затем разрешает class через container->get. Опция withGlobal=false исключает global и app-global, но не список конкретного app и не middleware route/controller.
Области конфигурации
Заголовок раздела «Области конфигурации»| Ключ | Область |
|---|---|
| @ | Глобальная, без plugin |
| пустая строка | Общая для текущего plugin/application namespace |
| имя приложения | Конкретное приложение |
| static | Статические ресурсы |
| plugin.{plugin}.{app} | Явно указанный plugin/app при стандартном app.plugin_alias |
При встрече @ loader сбрасывает локальную переменную plugin, которая затем используется для следующих ключей того же load. Поэтому не смешивайте @ и неявные plugin-scoped entries в одном списке: последующие записи могут попасть в иной scope. Используйте отдельные вызовы/явные plugin-ключи и проверяйте результат сборки.
Порядок
Заголовок раздела «Порядок»GetMiddleware возвращает array_reverse от объединения global, app-global, app и локальных controller/route списков. Engine-HTTP выполняет array_reduce и создаёт вложенные closures, поэтому первый объявленный global оказывается внешним обработчиком. Возврат после handler идёт в обратном порядке.
Маршрутный RouteObject::getMiddleware предварительно разворачивается отдельно. Контроллер определяется как массив с именем класса в первом элементе; готовый объект в таком массиве не проходит эту ветку reflection.
Install
Заголовок раздела «Install»Install копирует отсутствующий middleware.php. Поставляемая конфигурация содержит пустой список для ключа ”. Существующая конфигурация не заменяется автоматически, а регистрация класса в Composer autoload сама по себе middleware не включает.