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);
}