Питання на співбесіді: Middleware
Найпопулярніші питання з реальних Laravel/PHP співбесід для всіх рівнів
3 питання
Middleware - це шар фільтрації HTTP-запитів, що «обгортає» обробку: код може виконатися до того, як запит дійде до контролера, і/або після формування відповіді. Уявіть це як серію «застав», крізь які проходить кожен запит.
Створити middleware:
php artisan make:middleware EnsureUserIsActive
Логіка - у методі handle(); ключове - викликати $next($request), щоб передати запит далі конвеєром:
public function handle(Request $request, Closure $next): Response
{
if (! $request->user()?->is_active) {
return redirect('login'); // перервати конвеєр
}
return $next($request); // пропустити далі
}
Реєстрація та призначення (у Laravel 11+ - у bootstrap/app.php), застосування до маршрутів:
Route::get('/dashboard', ...)->middleware('auth');
Route::middleware(['auth', 'verified'])->group(fn () => ...);
Типові вбудовані middleware: auth (автентифікація), throttle (обмеження частоти), verified (підтверджений email), signed (підписані URL). Middleware - основа для авторизації, логування, CORS, локалізації.
Звичайне middleware працює навколо запиту: щось робить до $next($request), щось - після, але завжди до того, як відповідь піде користувачу.
Terminable middleware виконується після відправлення відповіді:
class LogRequestDuration
{
public function handle(Request $request, Closure $next): Response
{
return $next($request);
}
public function terminate(Request $request, Response $response): void
{
// Користувач уже отримав сторінку - це його не затримує.
RequestLog::create([
'path' => $request->path(),
'status' => $response->getStatusCode(),
'duration' => microtime(true) - LARAVEL_START,
]);
}
}
Метод terminate() викликається з $app->terminate() після send().
Важлива умова: це працює лише коли сервер уміє віддати відповідь і продовжити виконання - тобто на FastCGI з fastcgi_finish_request(). За іншої конфігурації користувач усе одно чекатиме.
Ще одна деталь: за замовчуванням у terminate() потрапляє новий екземпляр middleware. Якщо потрібен той самий - зареєструйте його синглтоном:
$this->app->singleton(LogRequestDuration::class);
Для чого доречно: запис аналітики, логування тривалості, дрібне прибирання - те, що не впливає на відповідь.
Для чого ні: будь-що довге. Воно все одно тримає PHP-воркер зайнятим, тож ця робота належить у чергу, а не в terminate().
Middleware утворюють «цибулю» навколо обробки запиту: кожен бачить запит на вході й відповідь на виході у зворотному порядку. Порядок вирішує, бо одні залежать від роботи інших.
Зазвичай він визначається місцем реєстрації: глобальні → групові → маршрутні. Але middleware, призначені маршруту, сортуються за списком пріоритетів, а не за тим, як їх перелічили в ->middleware([...]).
Чому це важливо. SubstituteBindings резолвить {post} у модель. Політика, що перевіряє власника поста, має спрацювати після цього - інакше отримає рядок замість моделі. Так само аутентифікація мусить йти раніше за авторизацію: перевіряти права нема в кого, поки користувач не визначений.
Задати свій порядок у bootstrap/app.php:
->withMiddleware(function (Middleware $middleware) {
$middleware->priority([
\Illuminate\Cookie\Middleware\EncryptCookies::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\Auth\Middleware\Authenticate::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\App\Http\Middleware\EnsureSubscribed::class,
\Illuminate\Auth\Middleware\Authorize::class,
]);
})
Ті, що є в списку, ідуть у вказаному порядку; решта - за порядком призначення.
Ознака, що ви натрапили саме на це: middleware працює на одному маршруті й «не бачить» даних на іншому, хоча код той самий. Зазвичай це означає, що щось потрібне ще не встигло відпрацювати.
Порада: не переставляйте наосліп. Спершу зʼясуйте, від чого залежить ваше middleware - від сесії, від резолвленої моделі, від автентифікованого користувача, - і поставте його після цього.