Middleware
Вступ
Middleware надають зручний механізм для перевірки та фільтрації HTTP-запитів, що надходять до вашого застосунку. Наприклад, Laravel включає middleware, яке перевіряє, чи користувач вашого застосунку автентифікований. Якщо користувач не автентифікований, middleware перенаправить користувача на екран входу вашого застосунку. Однак, якщо користувач автентифікований, middleware дозволить запиту продовжити подальше виконання в застосунку.
Додаткове middleware може бути написане для виконання різноманітних завдань, окрім автентифікації. Наприклад, middleware для логування може записувати всі вхідні запити до вашого застосунку. У Laravel включено різноманітне middleware, включаючи 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.
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) {
$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) { $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) {
$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 () {
// ...
});
Laravel'и Стандартні Групи Middleware
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\ValidateCsrfToken |
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) {
$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 у стандартних групах middleware Laravel web та api, ви можете повністю перевизначити ці групи. Наведений нижче приклад визначить групи middleware web та api з їхніми стандартними middleware, дозволяючи вам налаштовувати їх за потреби:
->withMiddleware(function (Middleware $middleware) { $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\ValidateCsrfToken::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) {
$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 or Illuminate\Routing\Middleware\ThrottleRequestsWithRedis |
verified |
Illuminate\Auth\Middleware\EnsureEmailIsVerified |
Сортування Middleware
Рідко, але може виникнути потреба, щоб ваше middleware виконувалося в певному порядку, але ви не маєте контролю над їх порядком, коли вони призначені маршруту. У таких ситуаціях ви можете вказати пріоритет вашого middleware, використовуючи метод priority у файлі bootstrap/app.php вашого застосунку:
->withMiddleware(function (Middleware $middleware) {
$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\ValidateCsrfToken::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
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-відповідь була відправлена до браузера. Якщо ви визначите метод terminate у вашому middleware і ваш веб-сервер використовує 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); }
