Middleware в Laravel
Middleware — это фильтры HTTP-запросов, которые выполняются до или после запроса.
Основы
Создание middleware
bash
php artisan make:middleware CheckAgeСтруктура middleware
php
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class CheckAge
{
/**
* Обработка входящего запроса
*/
public function handle(Request $request, Closure $next): Response
{
if ($request->age <= 18) {
return redirect('home');
}
return $next($request);
}
}Типы Middleware
Before Middleware
Выполняется ДО передачи запроса контроллеру.
php
class BeforeMiddleware
{
public function handle(Request $request, Closure $next): Response
{
// Логика ДО обработки запроса
Log::info('Request received: ' . $request->path());
return $next($request);
}
}After Middleware
Выполняется ПОСЛЕ обработки запроса контроллером.
php
class AfterMiddleware
{
public function handle(Request $request, Closure $next): Response
{
$response = $next($request);
// Логика ПОСЛЕ обработки запроса
$response->header('X-Custom-Header', 'value');
return $response;
}
}Terminable Middleware
Выполняется после отправки ответа браузеру.
php
class TerminableMiddleware
{
public function handle(Request $request, Closure $next): Response
{
return $next($request);
}
public function terminate(Request $request, Response $response): void
{
// Логика после отправки ответа
// Полезно для логирования, аналитики
Log::info('Request completed', [
'path' => $request->path(),
'status' => $response->status(),
]);
}
}Регистрация Middleware
Global Middleware
Применяется ко всем запросам.
php
// bootstrap/app.php (Laravel 11+)
->withMiddleware(function (Middleware $middleware) {
$middleware->append(\App\Http\Middleware\LogRequests::class);
})
// Или в начало стека
$middleware->prepend(\App\Http\Middleware\CheckMaintenance::class);Route Middleware
Применяется к конкретным маршрутам.
php
// bootstrap/app.php (Laravel 11+)
->withMiddleware(function (Middleware $middleware) {
$middleware->alias([
'auth' => \App\Http\Middleware\Authenticate::class,
'admin' => \App\Http\Middleware\CheckAdmin::class,
'verified' => \App\Http\Middleware\EnsureEmailIsVerified::class,
]);
})Middleware Groups
php
// bootstrap/app.php
->withMiddleware(function (Middleware $middleware) {
$middleware->group('web', [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
]);
$middleware->group('api', [
\Illuminate\Routing\Middleware\ThrottleRequests::class.':api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
]);
})Применение Middleware
К маршруту
php
Route::get('/profile', function () {
// ...
})->middleware('auth');
// Несколько middleware
Route::get('/admin', function () {
// ...
})->middleware(['auth', 'admin']);
// Исключение middleware
Route::get('/public', function () {
// ...
})->withoutMiddleware(['auth']);К группе маршрутов
php
Route::middleware(['auth'])->group(function () {
Route::get('/dashboard', function () { });
Route::get('/profile', function () { });
});
Route::middleware(['auth', 'verified', 'admin'])->group(function () {
Route::get('/admin/users', [AdminUserController::class, 'index']);
Route::get('/admin/settings', [AdminSettingsController::class, 'index']);
});В контроллере
php
class UserController extends Controller
{
public function __construct()
{
$this->middleware('auth');
$this->middleware('verified')->only(['create', 'store']);
$this->middleware('admin')->except(['index', 'show']);
}
}
// Или через интерфейс (Laravel 11+)
use Illuminate\Routing\Controllers\HasMiddleware;
use Illuminate\Routing\Controllers\Middleware;
class UserController extends Controller implements HasMiddleware
{
public static function middleware(): array
{
return [
'auth',
new Middleware('verified', only: ['create', 'store']),
new Middleware('admin', except: ['index', 'show']),
];
}
}Middleware с параметрами
php
class CheckRole
{
public function handle(Request $request, Closure $next, string $role): Response
{
if (!$request->user()->hasRole($role)) {
abort(403, 'Unauthorized');
}
return $next($request);
}
}
// Несколько параметров
class CheckRoleOrPermission
{
public function handle(Request $request, Closure $next, string $role, string $permission): Response
{
if (!$request->user()->hasRole($role) && !$request->user()->can($permission)) {
abort(403);
}
return $next($request);
}
}Использование с параметрами
php
Route::get('/admin', function () {
// ...
})->middleware('role:admin');
Route::get('/editor', function () {
// ...
})->middleware('role:editor,can_edit');
// В маршрутах
Route::middleware(['role:admin'])->group(function () {
// ...
});Встроенные Middleware
auth
php
// Требует аутентификации
Route::get('/dashboard', fn() => view('dashboard'))
->middleware('auth');
// С конкретным guard
Route::get('/admin', fn() => view('admin'))
->middleware('auth:admin');guest
php
// Только для гостей
Route::get('/login', fn() => view('login'))
->middleware('guest');verified
php
// Требует подтвержденный email
Route::get('/settings', fn() => view('settings'))
->middleware(['auth', 'verified']);throttle
php
// Rate limiting
Route::get('/api/users', fn() => User::all())
->middleware('throttle:60,1'); // 60 запросов в минуту
// Именованный limiter
Route::middleware(['throttle:api'])->group(function () {
Route::get('/users', [UserController::class, 'index']);
});signed
php
// Требует подписанный URL
Route::get('/unsubscribe/{user}', fn(User $user) => $user->unsubscribe())
->name('unsubscribe')
->middleware('signed');
// Генерация подписанного URL
$url = URL::signedRoute('unsubscribe', ['user' => $user->id]);
$url = URL::temporarySignedRoute('unsubscribe', now()->addMinutes(30), ['user' => $user->id]);can (авторизация)
php
Route::put('/post/{post}', function (Post $post) {
// ...
})->middleware('can:update,post');
Route::post('/posts', function () {
// ...
})->middleware('can:create,App\Models\Post');Примеры Middleware
Проверка роли
php
class CheckRole
{
public function handle(Request $request, Closure $next, string ...$roles): Response
{
$user = $request->user();
if (!$user || !$user->hasAnyRole($roles)) {
if ($request->expectsJson()) {
return response()->json(['error' => 'Forbidden'], 403);
}
abort(403, 'Access denied');
}
return $next($request);
}
}
// Использование
Route::middleware('role:admin,moderator')->group(function () {
// ...
});Логирование запросов
php
class LogRequests
{
public function handle(Request $request, Closure $next): Response
{
$startTime = microtime(true);
$response = $next($request);
$duration = microtime(true) - $startTime;
Log::info('Request processed', [
'method' => $request->method(),
'path' => $request->path(),
'status' => $response->status(),
'duration' => round($duration * 1000, 2) . 'ms',
'user_id' => $request->user()?->id,
'ip' => $request->ip(),
]);
return $response;
}
}Локализация
php
class SetLocale
{
public function handle(Request $request, Closure $next): Response
{
// Из URL параметра
if ($request->has('lang')) {
$locale = $request->get('lang');
}
// Из сессии
elseif (session()->has('locale')) {
$locale = session()->get('locale');
}
// Из заголовка
elseif ($request->hasHeader('Accept-Language')) {
$locale = $request->getPreferredLanguage(['en', 'ru', 'uk']);
}
// По умолчанию
else {
$locale = config('app.locale');
}
if (in_array($locale, ['en', 'ru', 'uk'])) {
app()->setLocale($locale);
session()->put('locale', $locale);
}
return $next($request);
}
}Режим обслуживания
php
class CheckMaintenanceMode
{
public function handle(Request $request, Closure $next): Response
{
if (config('app.maintenance_mode')) {
// Разрешить доступ по IP
$allowedIps = config('app.maintenance_allowed_ips', []);
if (!in_array($request->ip(), $allowedIps)) {
return response()->view('maintenance', [], 503);
}
}
return $next($request);
}
}CORS (ручная реализация)
php
class HandleCors
{
public function handle(Request $request, Closure $next): Response
{
$response = $next($request);
$response->headers->set('Access-Control-Allow-Origin', '*');
$response->headers->set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
$response->headers->set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
return $response;
}
}Проверка подписки
php
class EnsureUserIsSubscribed
{
public function handle(Request $request, Closure $next): Response
{
if (!$request->user()?->subscribed('default')) {
if ($request->expectsJson()) {
return response()->json([
'error' => 'Subscription required'
], 402);
}
return redirect()->route('billing');
}
return $next($request);
}
}Добавление заголовков безопасности
php
class SecurityHeaders
{
public function handle(Request $request, Closure $next): Response
{
$response = $next($request);
$response->headers->set('X-Frame-Options', 'SAMEORIGIN');
$response->headers->set('X-Content-Type-Options', 'nosniff');
$response->headers->set('X-XSS-Protection', '1; mode=block');
$response->headers->set('Referrer-Policy', 'strict-origin-when-cross-origin');
$response->headers->set('Content-Security-Policy', "default-src 'self'");
return $response;
}
}Приоритет Middleware
php
// bootstrap/app.php
->withMiddleware(function (Middleware $middleware) {
$middleware->priority([
\App\Http\Middleware\StartSession::class,
\App\Http\Middleware\Authenticate::class,
\App\Http\Middleware\AuthorizeResource::class,
]);
})Исключения из CSRF
php
// bootstrap/app.php
->withMiddleware(function (Middleware $middleware) {
$middleware->validateCsrfTokens(except: [
'stripe/*',
'webhook/*',
]);
})