Middleware в Laravel: свои и встроенные без путаницы
Как устроен конвейер middleware в Laravel 11, чем группа отличается от алиаса, где регистрируется свой посредник и почему порядок в стеке решает исход запроса.

Задача: не пустить неоплаченного пользователя в API
У вас есть маршрут /api/reports, к которому должны попадать только пользователи с активной подпиской. Очевидное решение — проверка внутри контроллера: достали пользователя, посмотрели поле subscription_ends_at, если истекло — вернули 403. Работает ровно до второго такого маршрута. Потом до третьего. Через месяц проверка размазана по десяти контроллерам, в одном из них забыли часовой пояс, и клиент с истёкшей подпиской качает отчёты.
Middleware решает именно это: фильтр стоит на входе в маршрут, до контроллера, и решает — пропустить запрос дальше или оборвать. В Laravel 11 механизм регистрации ощутимо переехал по сравнению с 10.x, и половина гайдов в поиске показывает устаревший app/Http/Kernel.php, которого в новом скелете просто нет. Разберём, где что регистрируется теперь, чем встроенные посредники отличаются от ваших и почему порядок в стеке — не косметика.
Что такое middleware в терминах фреймворка
Middleware — слой между HTTP-запросом и его обработчиком.1 Запрос проходит через цепочку посредников по пути к контроллеру, ответ проходит через ту же цепочку в обратном порядке. Каждый посредник получает объект запроса и замыкание $next — вызов следующего звена. Если вы вернёте ответ вместо вызова $next, цепочка обрывается и контроллер не выполнится.
Middleware — это не «проверка перед действием». Это луковица: код до $next выполняется на входе, код после — на выходе, и один посредник может делать обе вещи.Встроенные посредники: что уже работает за вас
Laravel из коробки поставляет набор посредников, часть из которых применяется ко всем запросам глобально, часть — по алиасу на нужных маршрутах.
| Посредник | Алиас | Что делает |
|---|---|---|
| Authenticate | auth | Требует аутентифицированного пользователя, иначе редирект или 401 |
| EnsureEmailIsVerified | verified | Пускает только с подтверждённым email |
| ThrottleRequests | throttle | Ограничивает частоту запросов (rate limiting) |
| ValidateSignature | signed | Проверяет подпись URL |
| Authorize | can | Проверяет политику доступа (Gate/Policy) |
Полный актуальный список алиасов есть в исходнике класса, который конфигурирует приложение — в Laravel 11 это связано с Illuminate\Foundation\Configuration\Middleware. Не заучивайте список наизусть: он может дополняться между минорными версиями, сверяйтесь с документацией своей версии.
Три уровня применения
- Глобальный — выполняется на каждом HTTP-запросе. Сюда попадают вещи вроде обрезки строк и подмены пустых строк на null.
- Группа — набор посредников под одним именем. Стандартные группы —
web(сессии, куки, CSRF) иapi(без сессий, чаще с throttle). - Алиас маршрута — короткое имя, которое вешается на конкретный маршрут или группу маршрутов через
->middleware('auth').
Свой посредник: проверка подписки
Вернёмся к задаче. Создаём посредник командой Artisan (Laravel 11.x):
php artisan make:middleware EnsureSubscriptionActiveФайл появится в app/Http/Middleware/EnsureSubscriptionActive.php. Заполняем метод handle:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class EnsureSubscriptionActive
{
public function handle(Request $request, Closure $next): Response
{
$user = $request->user();
// Пользователь без активной подписки дальше не проходит
if (! $user || ! $user->hasActiveSubscription()) {
abort(403, 'Требуется активная подписка.');
}
return $next($request);
}
}Метод hasActiveSubscription() здесь — ваш метод на модели User, а не встроенный; пример подразумевает, что вы его определили сами. Проверка $request->user() на null стоит не зря: этот посредник должен работать только после auth, но если кто-то повесит его без auth, явная проверка спасёт от фатальной ошибки на hasActiveSubscription у null.
Где регистрировать в Laravel 11
Здесь и ломаются старые инструкции. В Laravel 11 нет app/Http/Kernel.php — регистрация посредников переехала в bootstrap/app.php, в замыкание withMiddleware. Чтобы задать алиас для своего посредника:
// bootstrap/app.php
use App\Http\Middleware\EnsureSubscriptionActive;
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Middleware;
return Application::configure(basePath: dirname(__DIR__))
->withMiddleware(function (Middleware $middleware) {
// Свой алиас для маршрутов
$middleware->alias([
'subscribed' => EnsureSubscriptionActive::class,
]);
})
->create();После этого посредник доступен на маршруте по имени:
// routes/api.php
Route::middleware(['auth:sanctum', 'subscribed'])->group(function () {
Route::get('/reports', [ReportController::class, 'index']);
});Проверка: обратитесь к /api/reports токеном пользователя с истёкшей подпиской — должны получить 403 с текстом «Требуется активная подписка». Токеном активного пользователя — контроллер отработает штатно.
Порядок в стеке решает исход
Порядок посредников на маршруте — не декоративный. ['auth:sanctum', 'subscribed'] и ['subscribed', 'auth:sanctum'] ведут себя по-разному. В первом случае сначала отработает аутентификация, установит пользователя в запрос, и только потом ваш subscribed увидит непустой $request->user(). Во втором ваш посредник выполнится раньше auth, пользователя ещё не будет, сработает ветка с abort(403) — и вместо честного «нужно войти» неаутентифицированный гость получит «нужна подписка». Логика поехала.
Для глобальных посредников, где порядок нельзя задать на уровне маршрута, Laravel 11 даёт метод приоритета через $middleware->priority([...]) в том же withMiddleware. Синтаксис и доступные методы сверяйте с документацией вашей минорной версии — набор методов конфигуратора расширяется.
Параметры посредника
Посредник умеет принимать аргументы прямо из строки маршрута. Встроенный throttle так и работает: throttle:60,1 — шестьдесят запросов в минуту. В своём посреднике аргументы идут после $next:
public function handle(Request $request, Closure $next, string $plan): Response
{
if ($request->user()?->plan !== $plan) {
abort(403);
}
return $next($request);
}На маршруте передаётся через двоеточие: ->middleware('subscribed:pro'). Аргумент $plan получит строку pro.
Работа на выходе: код после $next
Иногда нужно не фильтровать запрос, а модифицировать ответ — добавить заголовок, залогировать время выполнения. Тогда логика ложится после вызова $next:
public function handle(Request $request, Closure $next): Response
{
$response = $next($request);
// Выполняется на обратном пути, ответ уже сформирован
$response->headers->set('X-App-Version', config('app.version'));
return $response;
}Помните про порядок луковицы: посредник, стоящий первым на входе, на выходе отработает последним. Если два посредника трогают один и тот же заголовок, победит тот, что ближе к контроллеру.
Если не получилось
- «Target class [subscribed] does not exist» — алиас не зарегистрирован в
bootstrap/app.phpлибо есть опечатка в имени. Проверьте вызовalias(). - Ищете
app/Http/Kernel.php, а его нет — вы на Laravel 11+, регистрация вbootstrap/app.php. На проекте, обновлённом с 10.x, Kernel мог сохраниться — тогда регистрируйте как раньше, скелет не мигрирует автоматически. - Посредник не срабатывает — маршрут не входит в группу с этим посредником, либо кеш маршрутов устарел. Выполните
php artisan route:clearи проверьтеphp artisan route:list— колонка Middleware покажет реальную цепочку. - Неаутентифицированный получает 403 вместо 401 — ваш посредник стоит перед
auth. Переставьте местами. - Изменения в
bootstrap/app.phpне подхватываются — сбросьте кеш конфигурации:php artisan config:clear.
Что делать дальше
Проверьте цепочку на боевых маршрутах через php artisan route:list --middleware — так видно фактический порядок для каждого маршрута, а не то, что вы предполагаете. Дальше стоит посмотреть в сторону terminable middleware (метод terminate) для тяжёлых операций после отдачи ответа клиенту — логирование, отправка метрик — и в сторону группировки своих посредников в именованную группу, если один и тот же набор висит на десятке маршрутов. Инвалидация кеша маршрутов и настройка rate limiting — отдельные темы, здесь мы разобрали только регистрацию и порядок.
1 Middleware (посредник) — промежуточный слой, через который проходит HTTP-запрос по пути к обработчику и ответ на обратном пути. Позволяет вынести сквозную логику (аутентификацию, логирование, заголовки) из контроллеров.
Контент-конвейер: Из одной идеи — текст, изображение, озвучка и ролик. Один заказ вместо четырёх.
Спросить за 60 ₽Источники: Laravel Documentation — Middleware, Laravel Documentation — Configuration (bootstrap/app.php)
Частые вопросы
У меня Laravel 10, а в статье bootstrap/app.php. Что делать?
Посредник не срабатывает, хотя я его повесил на маршрут. Почему?
Как передать в посредник несколько параметров?
Чем группа middleware отличается от алиаса?
Стоит ли класть тяжёлые операции в middleware?
Где посмотреть полный список встроенных посредников моей версии?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.