中间件
这是一篇协同翻译的文章,你可以点击『我来翻译』按钮来参与翻译。
中间件(Middleware)
介绍
中间件提供了一种便捷的机制,用于检查和过滤进入应用程序的 HTTP 请求。例如,Laravel 内置了一个用于验证用户是否已通过身份认证的中间件。如果用户未通过身份认证,中间件会将用户重定向到应用程序的登录页面。然而,如果用户已经通过认证,中间件将允许请求继续深入应用程序进行处理。
除了身份认证之外,还可以编写额外的中间件来执行各种任务。例如,日志中间件可以记录所有进入应用程序的请求。Laravel 内置了多种中间件,包括身份认证和 CSRF 防护等中间件;不过,所有用户自定义的中间件通常位于应用程序的 app/Http/Middleware 目录中。
定义中间件
要创建一个新的中间件,可以使用 make:middleware Artisan 命令:
php artisan make:middleware EnsureTokenIsValid
此命令会在 app/Http/Middleware 目录中创建一个新的 EnsureTokenIsValid 类。在这个中间件中,我们仅允许当传入的 token 参数与指定值匹配时访问该路由。否则,我们会将用户重定向回 /home URI:
<?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 与我们的密钥 token 不匹配,中间件将向客户端返回一个 HTTP 重定向;否则,请求将被继续传递到应用程序的更深层。为了让请求继续深入应用程序(即允许中间件“放行”请求),你应该使用 $request 调用 $next 回调。
最好将中间件想象成 HTTP 请求在到达应用程序之前必须通过的一系列“层(layers)”。每一层都可以检查请求,甚至可以完全拒绝该请求。
[!注意]
所有中间件都通过服务容器进行解析,因此你可以在中间件的构造函数中使用类型提示(type-hint)注入任何所需的依赖。
中间件与响应(Middleware and Responses)
当然,中间件既可以在请求继续深入应用程序之前执行任务,也可以在之后执行任务。例如,下面的中间件会在请求被应用程序处理之前执行某些操作:
<?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);
}
}
然而,下面这个中间件会在请求被应用程序处理之后执行其任务:
<?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;
}
}
注册中间件
全局中间件
如果你希望某个中间件在应用程序的每一个 HTTP 请求期间都运行,你可以将其添加到应用程序 bootstrap/app.php 文件中的全局中间件栈:
use App\Http\Middleware\EnsureTokenIsValid;
->withMiddleware(function (Middleware $middleware): void {
$middleware->append(EnsureTokenIsValid::class);
})
withMiddleware 闭包中提供的 $middleware 对象是 Illuminate\Foundation\Configuration\Middleware 的一个实例,它负责管理分配给应用程序路由的中间件。append 方法会将中间件添加到全局中间件列表的末尾。如果你希望将中间件添加到列表的开头,应使用 prepend 方法。
手动管理 Laravel 默认的全局中间件
如果你希望手动管理 Laravel 的全局中间件栈,可以通过 use 方法提供 Laravel 默认的全局中间件栈。然后,你可以根据需要调整默认中间件栈:
->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 方法:
use App\Http\Middleware\EnsureTokenIsValid;
Route::get('/profile', function () {
// ...
})->middleware(EnsureTokenIsValid::class);
你也可以通过向 middleware 方法传递一个中间件名称数组,为路由分配多个中间件:
Route::get('/', function () {
// ...
})->middleware([First::class, Second::class]);
排除中间件
在将中间件分配给一组路由时,有时你可能需要阻止某个中间件应用于组中的某一条特定路由。你可以使用 withoutMiddleware 方法来实现:
use App\Http\Middleware\EnsureTokenIsValid;
Route::middleware([EnsureTokenIsValid::class])->group(function () {
Route::get('/', function () {
// ...
});
Route::get('/profile', function () {
// ...
})->withoutMiddleware([EnsureTokenIsValid::class]);
});
你还可以将指定的一组中间件从整个路由组定义中排除:
use App\Http\Middleware\EnsureTokenIsValid;
Route::withoutMiddleware([EnsureTokenIsValid::class])->group(function () {
Route::get('/profile', function () {
// ...
});
});
withoutMiddleware 方法只能移除路由中间件(route middleware),而不适用于全局中间件。
中间件组
有时,你可能希望将多个中间件归为一个单独的键(key),以便更轻松地将它们分配给路由。你可以在应用程序的 bootstrap/app.php 文件中使用 appendToGroup 方法来实现:
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,
]);
})
中间件组可以像单个中间件一样,使用相同的语法分配给路由和控制器操作:
Route::get('/', function () {
// ...
})->middleware('group-name');
Route::middleware(['group-name'])->group(function () {
// ...
});
Laravel 默认的中间件组
Laravel 内置了预定义的 web 和 api 中间件组,其中包含了一些常见中间件,这些中间件通常会应用于 Web 路由和 API 路由。请记住,Laravel 会自动将这些中间件组应用到对应的 routes/web.php 和 routes/api.php 文件:
| 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` |
| api 中间件组 | | -------------------------------------------------- | | `Illuminate\Routing\Middleware\SubstituteBindings` |
If you would like to append or prepend middleware to these groups, you may use the web and api methods within your application's bootstrap/app.php file. The web and api methods are convenient alternatives to the appendToGroup method:
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,
]);
})
You may even replace one of Laravel's default middleware group entries with a custom middleware of your own:
use App\Http\Middleware\StartCustomSession;
use Illuminate\Session\Middleware\StartSession;
$middleware->web(replace: [
StartSession::class => StartCustomSession::class,
]);
Or, you may remove a middleware entirely:
$middleware->web(remove: [
StartSession::class,
]);
Manually Managing Laravel's Default Middleware Groups
If you would like to manually manage all of the middleware within Laravel's default web and api middleware groups, you may redefine the groups entirely. The example below will define the web and api middleware groups with their default middleware, allowing you to customize them as necessary:
->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,
]);
})
[!NOTE]
By default, thewebandapimiddleware groups are automatically applied to your application's correspondingroutes/web.phpandroutes/api.phpfiles by thebootstrap/app.phpfile.
Middleware Aliases
You may assign aliases to middleware in your application's bootstrap/app.php file. Middleware aliases allow you to define a short alias for a given middleware class, which can be especially useful for middleware with long class names:
use App\Http\Middleware\EnsureUserIsSubscribed;
->withMiddleware(function (Middleware $middleware): void {
$middleware->alias([
'subscribed' => EnsureUserIsSubscribed::class
]);
})
Once the middleware alias has been defined in your application's bootstrap/app.php file, you may use the alias when assigning the middleware to routes:
Route::get('/profile', function () {
// ...
})->middleware('subscribed');
For convenience, some of Laravel's built-in middleware are aliased by default. For example, the auth middleware is an alias for the Illuminate\Auth\Middleware\Authenticate middleware. Below is a list of the default middleware aliases:
| Alias | 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` |
Sorting Middleware
Rarely, you may need your middleware to execute in a specific order but not have control over their order when they are assigned to the route. In these situations, you may specify your middleware priority using the priority method in your application's bootstrap/app.php file:
->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 Parameters
Middleware can also receive additional parameters. For example, if your application needs to verify that the authenticated user has a given "role" before performing a given action, you could create an EnsureUserHasRole middleware that receives a role name as an additional argument.
Additional middleware parameters will be passed to the middleware after the $next argument:
<?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 parameters may be specified when defining the route by separating the middleware name and parameters with a ::
use App\Http\Middleware\EnsureUserHasRole;
Route::put('/post/{id}', function (string $id) {
// ...
})->middleware(EnsureUserHasRole::class.':editor');
Multiple parameters may be delimited by commas:
Route::put('/post/{id}', function (string $id) {
// ...
})->middleware(EnsureUserHasRole::class.':editor,publisher');
Terminable Middleware
Sometimes a middleware may need to do some work after the HTTP response has been sent to the browser. If you define a terminate method on your middleware and your web server is using FastCGI, the terminate method will automatically be called after the response is sent to the browser:
<?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
{
// ...
}
}
The terminate method should receive both the request and the response. Once you have defined a terminable middleware, you should add it to the list of routes or global middleware in your application's bootstrap/app.php file.
When calling the terminate method on your middleware, Laravel will resolve a fresh instance of the middleware from the service container. If you would like to use the same middleware instance when the handle and terminate methods are called, register the middleware with the container using the container's singleton method. Typically this should be done in the register method of your AppServiceProvider:
use App\Http\Middleware\TerminatingMiddleware;
/**
* Register any application services.
*/
public function register(): void
{
$this->app->singleton(TerminatingMiddleware::class);
}
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
Laravel 13 中文文档
关于 LearnKu
推荐文章: