Middleware: паттерн Chain of Responsibility
Что такое Middleware
Middleware — слой между запросом и контроллером.
Задачи:
- Аутентификация.
- CORS.
- Rate limiting.
- Логирование.
- Локализация.
Паттерн Chain of Responsibility
Цепочка обработчиков:
- Запрос идёт через первый middleware.
- Первый вызывает следующий.
- И так далее — до контроллера.
- Ответ идёт в обратном порядке.
Каждый может:
- Прервать (вернуть ответ).
- Изменить запрос/ответ.
- Пропустить дальше через
$next.
Простой пример
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class EnsureTokenIsValid
{
public function handle(Request $request, Closure $next): Response
{
if ($request->input('token') !== 'secret') {
return response('Access denied', 401);
}
return $next($request);
}
}
$next($request) — передать дальше.
Возврат ответа
Middleware может вернуть ответ раньше:
if (!auth()->check()) {
return redirect('/login');
}
return $next($request);
Цепочка прерывается.
Изменение запроса
public function handle(Request $request, Closure $next): Response
{
$request->merge(['source' => 'api']);
return $next($request);
}
merge() — добавить параметры.
Изменение ответа
public function handle(Request $request, Closure $next): Response
{
$response = $next($request);
$response->headers->set('X-App-Version', '1.0');
return $response;
}
После $next можно модифицировать ответ.
Before vs After
До $next — до обработки:
public function handle(Request $request, Closure $next): Response
{
// до
$response = $next($request);
// после
return $response;
}
Регистрация
Глобальный middleware
Для всех запросов — в bootstrap/app.php (Laravel 11+):
->withMiddleware(function (Middleware $middleware) {
$middleware->append(YourMiddleware::class);
$middleware->prepend(AnotherMiddleware::class);
})
append — в конец.
prepend — в начало.
Группа
->withMiddleware(function (Middleware $middleware) {
$middleware->group('web', [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Session\Middleware\StartSession::class,
]);
})
На маршрут
Route::get('/admin', fn() => view('admin'))
->middleware('auth', 'admin');
На контроллер
class AdminController
{
public function __construct()
{
$this->middleware('auth');
$this->middleware('admin')->only(['destroy']);
}
}
Параметры middleware
Передача параметров:
Route::get('/user/{id}', ...)->middleware('can:view,user');
В middleware:
public function handle(
Request $request,
Closure $next,
string $action,
string $model,
): Response { }
Порядок
Важен!
Порядок выполнения:
- Global (в порядке регистрации).
- Group (web/api).
- Route (на маршруте).
Возврат — в обратном порядке.
Встроенные Middleware
Основные:
Authenticate—auth.Authorize—can.VerifyCsrfToken—web.EnsureEmailIsVerified—verified.ThrottleRequests—throttle.EnsureFrontendRequestsAreStateful—sanctum.
Создание
php artisan make:middleware EnsureUserIsAdmin
Файл в app/Http/Middleware/.
Регистрация в bootstrap/app.php:
->withMiddleware(function (Middleware $middleware) {
$middleware->alias([
'admin' => \App\Http\Middleware\EnsureUserIsAdmin::class,
]);
})
Практика: Middleware для админа
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class EnsureUserIsAdmin
{
public function handle(Request $request, Closure $next): Response
{
if (!$request->user()?->is_admin) {
abort(403, 'Доступ только для администраторов');
}
return $next($request);
}
}
Использование:
Route::middleware('admin')->group(function () {
Route::get('/admin', [AdminController::class, 'index']);
Route::delete('/users/{user}', [UserController::class, 'destroy']);
});
Практика: логирование запросов
class LogRequests
{
public function __construct(
private LoggerInterface $logger,
) {}
public function handle(Request $request, Closure $next): Response
{
$start = microtime(true);
$response = $next($request);
$this->logger->info('Request', [
'method' => $request->method(),
'path' => $request->path(),
'status' => $response->getStatusCode(),
'time_ms' => round((microtime(true) - $start) * 1000, 2),
]);
return $response;
}
}
Terminable Middleware
Обработка после ответа:
class LogResponse
{
public function handle(Request $request, Closure $next): Response
{
return $next($request);
}
public function terminate(Request $request, Response $response): void
{
// выполнится после отправки ответа клиенту
}
}
После Response отправлен — для фоновых задач.
Kernel в Laravel 10
app/Http/Kernel.php:
protected $middleware = [
\App\Http\Middleware\TrustProxies::class,
\App\Http\Middleware\PreventRequestsDuringMaintenance::class,
];
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Session\Middleware\StartSession::class,
],
'api' => [
\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
'throttle:api',
],
];
protected $middlewareAliases = [
'auth' => \App\Http\Middleware\Authenticate::class,
'admin' => \App\Http\Middleware\EnsureUserIsAdmin::class,
];
Laravel 11+ — в bootstrap/app.php.
Практика
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
use App\Models\User;
class TrackUserActivity
{
public function handle(Request $request, Closure $next): Response
{
$response = $next($request);
/** @var User|null $user */
$user = $request->user();
if ($user !== null && $this->shouldTrack($request)) {
$user->forceFill([
'last_login_at' => now(),
])->saveQuietly();
}
return $response;
}
private function shouldTrack(Request $request): bool
{
return $request->isMethod('GET')
&& !$request->ajax()
&& !$request->is('admin/*');
}
}
Регистрация:
// bootstrap/app.php
->withMiddleware(function (Middleware $middleware) {
$middleware->append(\App\Http\Middleware\TrackUserActivity::class);
})
Итог
- Middleware — Chain of Responsibility.
$next($request)— передать дальше.- До/после — по расположению кода.
- Прерывание — вернуть Response.
- Порядок важен.
- Алиасы — для удобства.
terminate— после ответа.
Дальше — Eloquent: Active Record vs Data Mapper.