Route interceptors
Route interceptor — pre-handler хук у RouteStrategy.
Він викликається для кожного matched route після middleware-ланцюжка
League Route, але до prepareRequest() і до
резолву аргументів handler-а.
Ключова ідея: одне централізоване місце для глобальних правил
доступу, коли логіка спирається саме на метадані маршруту
(name, vars, HTTP-метод, path), а не на
response pipeline. Не треба дублювати «чи потрібен токен» у кожному
route file — і не треба писати middleware, яке все одно лише кидає
exception.
Для чого використовувати interceptors
- Global pre-check для всіх маршрутів: токен, роль, feature flag, tenant-контекст.
- Перевірка за іменем маршруту через
$route->getName()без дублювання правил у кожному route file. - Fail-fast: зупинити обробку до створення аргументів handler-а (дешевше і чистіше, ніж «напівстворений» FormRequest).
- Наскрізні аудиторські дії: контроль доступу, аудит спроб, стандартизовані domain-винятки.
Не використовуйте interceptor, якщо потрібно повернути або
модифікувати ResponseInterface у ланцюжку — тоді
PSR-15 middleware. Interceptor повертає void: або
пропускає, або кидає.
Контракт
Ядро не нав’язує policy-модель: interceptor сам вирішує, кидати
виняток чи пропускати запит далі. У intercept()
доступні $route->getName(), $route->getVars()
і container request (ще без route attributes — attributes з’являться
після prepareRequest()).
namespace Concept\Core\Http\Contracts;
interface RouteInterceptorInterface
{
public function intercept(Route $route, ServerRequestInterface $request): void;
}
Реєстрація
Interceptors реєструють у constructor
HttpKernelServiceProvider (або через
setInterceptors на Http brick у Stack) — глобально на
всі matched routes. Передавайте готовий instance або
Closure(ContainerInterface); class-string Core не
резолвить. Типовий патерн — фабрика з
$c->get(...), щоб DI зібрав залежності interceptor-а.
new HttpKernelServiceProvider(
routePaths: [$routesFile],
resolvers: [...],
interceptors: [
static fn (ContainerInterface $c) => $c->get(RequireAuthInterceptor::class),
],
);
Приклад
Типовий сценарій: немає Authorization → domain exception → ваше middleware / ErrorHandling перетворює на 401/403. Controller авторизацію «на всяк випадок» уже не дублює.
final class RequireAuthInterceptor implements RouteInterceptorInterface
{
public function intercept(Route $route, ServerRequestInterface $request): void
{
$token = $request->getHeaderLine('Authorization');
if ($token === '') {
throw new UnauthorizedException('Missing token.');
}
}
}
Порядок виконання
Interceptor працює в момент, коли route вже знайдено, але handler ще не запущено — ідеальне місце для «чи взагалі можна йти далі».
HTTP request
→ League Route middleware chain
→ RouteStrategy::invokeRouteCallable()
→ runInterceptors()
→ prepareRequest()
→ resolveArguments()
→ invoke handler
Interceptors vs middleware
| Interceptor | Middleware | |
|---|---|---|
| Контракт | RouteInterceptorInterface | PSR-15 |
| Де реєструвати | HttpKernelServiceProvider constructor | Route file |
| Область | Глобально на всі matched routes | Per route / group |
| Response | Через виняток або side-effect | Повертає ResponseInterface |
| Найкращий use-case | ACL/policy на рівні route | response pipeline, трансформація відповіді |
Події
Після кожного interceptor, якщо зареєстрований
EventDispatcherInterface, диспатчиться
RouteInterceptorInvoked з class-string interceptor і
об’єктом route. Коли: аудит / метрики без змін самого interceptor.
Коли ні: немає dispatcher — ядро просто пропускає подію.