Middleware
Вступ
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
{
/**
* Handle an incoming request.
*
* @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
{
// Perform action
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);
// Perform action
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 на початок списку, скористайтеся методом prepend.
Ручне керування типовими глобальними middleware Laravel
Якщо ви хочете керувати глобальним стеком middleware Laravel вручну, передайте типовий стек методу use. Далі ви можете коригувати його за потреби:
->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 масив їхніх імен:
Route::get('/', function () {
// ...
})->middleware([First::class, Second::class]);
Виключення 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 автоматично застосовує ці групи до відповідних файлів 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. Вони є зручною альтернативою методу 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 власним:
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, ви можете повністю перевизначити ці групи. Приклад нижче визначає групи 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,
]);
})
За замовчуванням групи
middlewarewebтаapiавтоматично застосовуються до відповідних файлівroutes/web.phpіroutes/api.phpвашого застосунку через файлbootstrap/app.php.
Псевдоніми middleware
Ви можете призначати middleware псевдоніми у файлі bootstrap/app.php вашого застосунку. Псевдоніми дозволяють визначити коротку назву для класу middleware, що особливо корисно для middleware з довгими іменами класів:
use App\Http\Middleware\EnsureUserIsSubscribed;
->withMiddleware(function (Middleware $middleware): void {
$middleware->alias([
'subscribed' => EnsureUserIsSubscribed::class
]);
})
Щойно псевдонім визначено у файлі bootstrap/app.php, ви можете використовувати його, призначаючи middleware маршрутам:
Route::get('/profile', function () {
// ...
})->middleware('subscribed');
Для зручності деякі вбудовані middleware Laravel мають псевдоніми за замовчуванням. Наприклад, auth - це псевдонім для middleware Illuminate\Auth\Middleware\Authenticate. Нижче наведено список типових псевдонімів:
| Псевдонім | 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): 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
Middleware можуть також отримувати додаткові параметри. Наприклад, якщо вашому застосунку потрібно перевірити, чи має автентифікований користувач певну «роль», перш ніж виконати дію, ви можете створити middleware EnsureUserHasRole, який отримує ім'я ролі як додатковий аргумент.
Додаткові параметри передаються middleware після аргументу $next:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class EnsureUserHasRole
{
/**
* Handle an incoming request.
*
* @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)) {
// Redirect...
}
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
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
return $next($request);
}
/**
* Handle tasks after the response has been sent to the browser.
*/
public function terminate(Request $request, Response $response): void
{
// ...
}
}
Метод terminate має отримувати і запит, і відповідь. Визначивши завершуваний middleware, додайте його до списку маршрутних чи глобальних middleware у файлі bootstrap/app.php вашого застосунку.
Викликаючи метод terminate вашого middleware, Laravel розв'яже з сервіс-контейнера новий екземпляр middleware. Якщо ви хочете використовувати той самий екземпляр під час викликів методів handle і terminate, зареєструйте middleware в контейнері методом singleton. Зазвичай це варто робити в методі register вашого AppServiceProvider:
use App\Http\Middleware\TerminatingMiddleware;
/**
* Register any application services.
*/
public function register(): void
{
$this->app->singleton(TerminatingMiddleware::class);
}