#Введение
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), нужно вызвать callback $next с аргументом $request.
Лучше всего представлять middleware как серию «слоёв», через которые HTTP-запросы должны пройти, прежде чем попасть в ваше приложение. Каждый слой может проверить запрос и даже полностью его отклонить.
Все middleware разрешаются через service container, поэтому вы можете указывать любые зависимости в конструкторе 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 в свойство $middleware класса app/Http/Kernel.php.
#Назначение Middleware маршрутам
Если вы хотите назначить middleware конкретным маршрутам, вызовите метод middleware при определении маршрута:
use App\Http\Middleware\Authenticate;
Route::get('/profile', function () {
// ...
})->middleware(Authenticate::class);
Вы можете назначить несколько middleware маршруту, передав массив имён middleware в метод middleware:
Route::get('/', function () {
// ...
})->middleware([First::class, Second::class]);
Для удобства вы можете назначать псевдонимы middleware в файле app/Http/Kernel.php вашего приложения. По умолчанию свойство $middlewareAliases этого класса содержит записи для middleware, включённых в Laravel. Вы можете добавить свои middleware в этот список и назначить им псевдонимы по своему выбору:
// Внутри класса App\Http\Kernel...
protected $middlewareAliases = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
];
После определения псевдонима middleware в HTTP-ядре, вы можете использовать этот псевдоним при назначении middleware маршрутам:
Route::get('/profile', function () {
// ...
})->middleware('auth');
#Исключение 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 под одним ключом, чтобы проще назначать их маршрутам. Это можно сделать с помощью свойства $middlewareGroups вашего HTTP-ядра.
Laravel включает предопределённые группы middleware web и api, которые содержат общие middleware, которые вы, возможно, захотите применить к вашим веб- и API-маршрутам. Помните, что эти группы middleware автоматически применяются сервис-провайдером App\Providers\RouteServiceProvider к маршрутам в соответствующих файлах web и api:
/**
* Группы middleware приложения.
*
* @var array
*/
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [
\Illuminate\Routing\Middleware\ThrottleRequests::class.':api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
];
Группы middleware можно назначать маршрутам и действиям контроллеров, используя тот же синтаксис, что и для отдельных middleware. Группы middleware упрощают назначение множества middleware маршруту одновременно:
Route::get('/', function () {
// ...
})->middleware('web');
Route::middleware(['web'])->group(function () {
// ...
});
Из коробки группы middleware web и api автоматически применяются к соответствующим файлам маршрутов routes/web.php и routes/api.php вашим сервис-провайдером App\Providers\RouteServiceProvider.
#Сортировка Middleware
Редко бывает нужно, чтобы middleware выполнялись в определённом порядке, но при этом вы не контролируете порядок их назначения маршруту. В этом случае вы можете указать приоритет middleware с помощью свойства $middlewarePriority в файле app/Http/Kernel.php. По умолчанию это свойство может отсутствовать в вашем HTTP-ядре. Если его нет, вы можете добавить его с приведённым ниже определением по умолчанию:
/**
* Список middleware, отсортированных по приоритету.
*
* Это заставляет не-глобальное middleware всегда выполняться в заданном порядке.
*
* @var string[]
*/
protected $middlewarePriority = [
\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\Contracts\Auth\Middleware\AuthenticatesRequests::class,
\Illuminate\Routing\Middleware\ThrottleRequests::class,
\Illuminate\Routing\Middleware\ThrottleRequestsWithRedis::class,
\Illuminate\Contracts\Session\Middleware\AuthenticatesSessions::class,
\Illuminate\Routing\Middleware\SubstituteBindings::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
{
/**
* Обработка входящего запроса.
*
* @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 и параметры двоеточием ::
Route::put('/post/{id}', function (string $id) {
// ...
})->middleware('role:editor');
Несколько параметров можно разделять запятыми:
Route::put('/post/{id}', function (string $id) {
// ...
})->middleware('role: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 в файле app/Http/Kernel.php.
При вызове метода terminate для вашего middleware Laravel создаст новый экземпляр middleware через service container. Если вы хотите использовать один и тот же экземпляр middleware при вызове методов handle и terminate, зарегистрируйте middleware в контейнере с помощью метода singleton. Обычно это делается в методе register вашего AppServiceProvider:
use App\Http\Middleware\TerminatingMiddleware;
/**
* Регистрация сервисов приложения.
*/
public function register(): void
{
$this->app->singleton(TerminatingMiddleware::class);
}