Middleware — Laravel
Middleware дає зручний механізм для перевірки та фільтрації HTTP-запитів, які надходять до вашого застосунку. Наприклад, Laravel містить middleware, який перевіряє, чи автентифікований користувач вашого застосунку. Якщо користувач не автентифікований, middleware перенаправить його на екран входу. Проте якщо користувач автентифікований, middleware дозволить запиту пройти далі в застосунок.
Можна написати додаткові middleware для виконання найрізноманітніших завдань, окрім автентифікації. Наприклад, middleware для логування може записувати всі вхідні запити до вашого застосунку. Laravel містить цілу низку middleware, зокрема для автентифікації та CSRF-захисту; проте всі визначені користувачем middleware зазвичай розташовані в теці app/Http/Middleware вашого застосунку.
Визначення middleware
Щоб створити новий middleware, скористайтеся Artisan-командою make:middleware:
php artisan make:middleware EnsureTokenIsValid
Ця команда помістить новий клас EnsureTokenIsValid у теку app/Http/Middleware. У цьому middleware ми дозволимо доступ до маршруту лише тоді, коли переданий вхідний параметр token збігається з визначеним значенням. Інакше ми перенаправимо користувачів назад на URI /home:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class EnsureTokenIsValid
{
/**
* Обробити вхідний запит.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
if ($request->input('token') !== 'my-secret-token') {
return redirect('/home');
}
return $next($request);
}
}
Як бачите, якщо переданий token не збігається з нашим секретним токеном, middleware поверне клієнту HTTP-перенаправлення; інакше запит буде передано далі в застосунок. Щоб передати запит глибше в застосунок (дозволивши middleware «пропустити» його), потрібно викликати колбек $next із $request.
Найкраще уявляти middleware як послідовність «шарів», крізь які HTTP-запити мають пройти, перш ніж дістануться вашого застосунку. Кожен шар може перевірити запит і навіть повністю його відхилити.
Примітка Усі middleware розвʼязуються через контейнер сервісів, тож у конструкторі middleware ви можете вказати type-hint для будь-яких потрібних залежностей.
Middleware і відповіді
Звісно, middleware може виконувати завдання до або після передавання запиту глибше в застосунок. Наприклад, наступний middleware виконує певне завдання перед тим, як запит опрацює застосунок:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class BeforeMiddleware
{
public function handle(Request $request, Closure $next): Response
{
// Виконати дію
return $next($request);
}
}
А цей middleware виконує своє завдання після того, як запит опрацював застосунок:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class AfterMiddleware
{
public function handle(Request $request, Closure $next): Response
{
$response = $next($request);
// Виконати дію
return $response;
}
}
Реєстрація middleware
Глобальні middleware
Якщо ви хочете, щоб middleware виконувався під час кожного HTTP-запиту до вашого застосунку, ви можете додати його до глобального стека middleware у файлі bootstrap/app.php вашого застосунку:
use App\Http\Middleware\EnsureTokenIsValid;
->withMiddleware(function (Middleware $middleware): void {
$middleware->append(EnsureTokenIsValid::class);
})
Обʼєкт $middleware, який передається в замикання withMiddleware, є екземпляром Illuminate\Foundation\Configuration\Middleware і відповідає за керування middleware, призначеними маршрутам вашого застосунку. Метод append додає middleware у кінець списку глобальних middleware. Якщо ви хочете додати middleware на початок списку, скористайтеся методом prepend.
Ручне керування типовими глобальними middleware Laravel
Якщо ви хочете керувати глобальним стеком middleware Laravel вручну, ви можете передати типовий стек глобальних middleware Laravel у метод use. Далі ви можете за потреби скоригувати типовий стек middleware:
->withMiddleware(function (Middleware $middleware): void {
$middleware->use([
\Illuminate\Foundation\Http\Middleware\InvokeDeferredCallbacks::class,
// \Illuminate\Http\Middleware\TrustHosts::class,
\Illuminate\Http\Middleware\TrustProxies::class,
\Illuminate\Http\Middleware\HandleCors::class,
\Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance::class,
\Illuminate\Http\Middleware\ValidatePostSize::class,
\Illuminate\Foundation\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
]);
})
Призначення middleware маршрутам
Якщо ви хочете призначити middleware конкретним маршрутам, ви можете викликати метод middleware під час визначення маршруту:
use App\Http\Middleware\EnsureTokenIsValid;
Route::get('/profile', function () {
// ...
})->middleware(EnsureTokenIsValid::class);
Ви можете призначити маршруту кілька middleware, передавши в метод middleware масив імен middleware:
Route::get('/', function () {
// ...
})->middleware([First::class, Second::class]);
Виключення middleware
Призначаючи middleware групі маршрутів, іноді може знадобитися не застосовувати цей middleware до окремого маршруту всередині групи. Це можна зробити за допомогою методу withoutMiddleware:
use App\Http\Middleware\EnsureTokenIsValid;
Route::middleware([EnsureTokenIsValid::class])->group(function () {
Route::get('/', function () {
// ...
});
Route::get('/profile', function () {
// ...
})->withoutMiddleware([EnsureTokenIsValid::class]);
});
Ви також можете виключити певний набір middleware для цілої групи визначень маршрутів:
use App\Http\Middleware\EnsureTokenIsValid;
Route::withoutMiddleware([EnsureTokenIsValid::class])->group(function () {
Route::get('/profile', function () {
// ...
});
});
Метод withoutMiddleware може видаляти лише middleware маршрутів і не застосовується до глобальних middleware.
Групи middleware
Іноді може виникнути потреба обʼєднати кілька middleware під одним ключем, щоб їх було легше призначати маршрутам. Це можна зробити за допомогою методу appendToGroup у файлі bootstrap/app.php вашого застосунку:
use App\Http\Middleware\First;
use App\Http\Middleware\Second;
->withMiddleware(function (Middleware $middleware): void {
$middleware->appendToGroup('group-name', [
First::class,
Second::class,
]);
$middleware->prependToGroup('group-name', [
First::class,
Second::class,
]);
})
Групи middleware можна призначати маршрутам і діям контролерів тим самим синтаксисом, що й окремі middleware:
Route::get('/', function () {
// ...
})->middleware('group-name');
Route::middleware(['group-name'])->group(function () {
// ...
});
Типові групи middleware Laravel
Laravel містить попередньо визначені групи middleware web та api, які містять поширені middleware, що ви можете захотіти застосувати до своїх веб- та API-маршрутів. Памʼятайте, що Laravel автоматично застосовує ці групи middleware до відповідних файлів routes/web.php та routes/api.php:
Група middleware web |
|---|
Illuminate\Cookie\Middleware\EncryptCookies |
Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse |
Illuminate\Session\Middleware\StartSession |
Illuminate\View\Middleware\ShareErrorsFromSession |
Illuminate\Foundation\Http\Middleware\PreventRequestForgery |
Illuminate\Routing\Middleware\SubstituteBindings |
Група middleware api |
|---|
Illuminate\Routing\Middleware\SubstituteBindings |
Якщо ви хочете додати middleware у кінець або на початок цих груп, ви можете скористатися методами web та api у файлі bootstrap/app.php вашого застосунку. Методи web та api є зручною альтернативою методу appendToGroup:
use App\Http\Middleware\EnsureTokenIsValid;
use App\Http\Middleware\EnsureUserIsSubscribed;
->withMiddleware(function (Middleware $middleware): void {
$middleware->web(append: [
EnsureUserIsSubscribed::class,
]);
$middleware->api(prepend: [
EnsureTokenIsValid::class,
]);
})
Ви навіть можете замінити один із записів типової групи middleware Laravel власним middleware:
use App\Http\Middleware\StartCustomSession;
use Illuminate\Session\Middleware\StartSession;
$middleware->web(replace: [
StartSession::class => StartCustomSession::class,
]);
Або ж ви можете повністю видалити middleware:
$middleware->web(remove: [
StartSession::class,
]);
Ручне керування типовими групами middleware Laravel
Якщо ви хочете вручну керувати всіма middleware всередині типових груп web та api Laravel, ви можете повністю перевизначити ці групи. Наведений нижче приклад визначає групи middleware web та api з їхніми типовими middleware, дозволяючи вам налаштувати їх за потреби:
->withMiddleware(function (Middleware $middleware): void {
$middleware->group('web', [
\Illuminate\Cookie\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\Illuminate\Foundation\Http\Middleware\PreventRequestForgery::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
// \Illuminate\Session\Middleware\AuthenticateSession::class,
]);
$middleware->group('api', [
// \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
// 'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
]);
})
Примітка Типово групи middleware
webтаapiавтоматично застосовуються до відповідних файлівroutes/web.phpтаroutes/api.phpвашого застосунку через файлbootstrap/app.php.
Псевдоніми middleware
Ви можете призначати middleware псевдоніми у файлі bootstrap/app.php вашого застосунку. Псевдоніми middleware дозволяють визначити коротке скорочення для певного класу middleware, що особливо корисно для middleware з довгими іменами класів:
use App\Http\Middleware\EnsureUserIsSubscribed;
->withMiddleware(function (Middleware $middleware): void {
$middleware->alias([
'subscribed' => EnsureUserIsSubscribed::class
]);
})
Щойно псевдонім middleware визначено у файлі bootstrap/app.php вашого застосунку, ви можете використовувати його при призначенні middleware маршрутам:
Route::get('/profile', function () {
// ...
})->middleware('subscribed');
Для зручності деякі вбудовані middleware Laravel типово мають псевдоніми. Наприклад, middleware auth — це псевдонім для middleware Illuminate\Auth\Middleware\Authenticate. Нижче наведено список типових псевдонімів middleware:
| Псевдонім | Middleware |
|---|---|
auth |
Illuminate\Auth\Middleware\Authenticate |
auth.basic |
Illuminate\Auth\Middleware\AuthenticateWithBasicAuth |
auth.session |
Illuminate\Session\Middleware\AuthenticateSession |
cache.headers |
Illuminate\Http\Middleware\SetCacheHeaders |
can |
Illuminate\Auth\Middleware\Authorize |
guest |
Illuminate\Auth\Middleware\RedirectIfAuthenticated |
password.confirm |
Illuminate\Auth\Middleware\RequirePassword |
precognitive |
Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests |
signed |
Illuminate\Routing\Middleware\ValidateSignature |
subscribed |
\Spark\Http\Middleware\VerifyBillableIsSubscribed |
throttle |
Illuminate\Routing\Middleware\ThrottleRequests або Illuminate\Routing\Middleware\ThrottleRequestsWithRedis |
verified |
Illuminate\Auth\Middleware\EnsureEmailIsVerified |
Сортування middleware
Зрідка може виникнути потреба, щоб ваші middleware виконувалися в певному порядку, але ви не маєте контролю над їхнім порядком під час призначення маршруту. У таких ситуаціях ви можете задати пріоритет middleware за допомогою методу priority у файлі bootstrap/app.php вашого застосунку:
->withMiddleware(function (Middleware $middleware): void {
$middleware->priority([
\Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests::class,
\Illuminate\Cookie\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\Illuminate\Foundation\Http\Middleware\PreventRequestForgery::class,
\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
\Illuminate\Routing\Middleware\ThrottleRequests::class,
\Illuminate\Routing\Middleware\ThrottleRequestsWithRedis::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\Illuminate\Contracts\Auth\Middleware\AuthenticatesRequests::class,
\Illuminate\Auth\Middleware\Authorize::class,
]);
})
Якщо ви хочете додати middleware до наявного списку пріоритетів, не замінюючи його, ви можете скористатися методами prependToPriorityList або appendToPriorityList. Метод prependToPriorityList вставляє вказаний middleware перед іншим middleware, а метод appendToPriorityList вставляє його після іншого middleware:
->withMiddleware(function (Middleware $middleware): void {
$middleware->prependToPriorityList(
before: \Illuminate\Routing\Middleware\SubstituteBindings::class,
prepend: \App\Http\Middleware\EnsureTokenIsValid::class,
);
$middleware->appendToPriorityList(
after: \Illuminate\Routing\Middleware\SubstituteBindings::class,
append: \App\Http\Middleware\EnsureUserIsSubscribed::class,
);
})
Аргументи before та after також можуть бути масивом класів middleware.
Параметри middleware
Middleware також можуть отримувати додаткові параметри. Наприклад, якщо вашому застосунку потрібно перевірити, що автентифікований користувач має певну «роль», перш ніж виконати певну дію, ви можете створити middleware EnsureUserHasRole, який отримує імʼя ролі як додатковий аргумент.
Додаткові параметри middleware передаються в middleware після аргументу $next:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class EnsureUserHasRole
{
/**
* Обробити вхідний запит.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next, string $role): Response
{
if (! $request->user()->hasRole($role)) {
// Перенаправлення...
}
return $next($request);
}
}
Параметри middleware можна вказати під час визначення маршруту, відокремивши імʼя middleware та параметри символом ::
use App\Http\Middleware\EnsureUserHasRole;
Route::put('/post/{id}', function (string $id) {
// ...
})->middleware(EnsureUserHasRole::class.':editor');
Кілька параметрів можна розділяти комами:
Route::put('/post/{id}', function (string $id) {
// ...
})->middleware(EnsureUserHasRole::class.':editor,publisher');
Завершувані middleware
Іноді middleware може знадобитися виконати певну роботу після того, як HTTP-відповідь уже надіслано браузеру. Якщо ви визначите в своєму middleware метод terminate, а ваш веб-сервер використовує FastCGI, метод terminate буде автоматично викликано після надсилання відповіді браузеру:
<?php
namespace Illuminate\Session\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class TerminatingMiddleware
{
/**
* Обробити вхідний запит.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
return $next($request);
}
/**
* Обробити завдання після того, як відповідь надіслано браузеру.
*/
public function terminate(Request $request, Response $response): void
{
// ...
}
}
Метод terminate має отримувати і запит, і відповідь. Щойно ви визначили завершуваний middleware, вам слід додати його до списку маршрутів або глобальних middleware у файлі bootstrap/app.php вашого застосунку.
Викликаючи метод terminate вашого middleware, Laravel розвʼяже свіжий екземпляр middleware з контейнера сервісів. Якщо ви хочете використовувати той самий екземпляр middleware під час виклику методів handle і terminate, зареєструйте middleware в контейнері за допомогою методу контейнера singleton. Зазвичай це слід робити в методі register вашого AppServiceProvider:
use App\Http\Middleware\TerminatingMiddleware;
/**
* Зареєструвати будь-які сервіси застосунку.
*/
public function register(): void
{
$this->app->singleton(TerminatingMiddleware::class);
}
Виправити терміни, дописати розділ або взяти нову главу може кожен. Термінологію узгоджуємо в глосарії, щоб переклад лишався однорідним.