Laravel Middleware的高级应用:全局中间件、路由中间件与参数化中间件的设计

Laravel Middleware 的高级应用:全局中间件、路由中间件与参数化中间件的设计

大家好,今天我们来深入探讨 Laravel Middleware 的高级应用,包括全局中间件、路由中间件以及参数化中间件的设计。Middleware 在 Laravel 框架中扮演着至关重要的角色,它允许我们在请求到达应用程序之前或之后,对请求进行过滤、修改或增强。掌握 Middleware 的高级应用,可以帮助我们构建更加健壮、安全和可维护的应用程序。

一、Middleware 的基本概念回顾

在深入高级应用之前,我们先简单回顾一下 Middleware 的基本概念。

Middleware 本质上是一个请求处理的“中间层”,它接收一个 HTTP 请求,可以对请求进行处理,然后传递给下一个 Middleware 或最终的应用程序。Middleware 也可以在应用程序处理完请求后,对响应进行处理。

Laravel 中,Middleware 通常是一个类,它包含一个 handle 方法。这个 handle 方法接收两个参数:

  1. $request: HTTP 请求对象。
  2. $next: 一个闭包,代表下一个 Middleware 或最终的应用程序。

Middleware 的基本结构如下:

<?php

namespace AppHttpMiddleware;

use Closure;
use IlluminateHttpRequest;

class ExampleMiddleware
{
    /**
     * Handle an incoming request.
     *
     * @param  IlluminateHttpRequest  $request
     * @param  Closure(IlluminateHttpRequest): (IlluminateHttpResponse|IlluminateHttpRedirectResponse)  $next
     * @return IlluminateHttpResponse|IlluminateHttpRedirectResponse
     */
    public function handle(Request $request, Closure $next)
    {
        // 在请求到达应用程序之前执行的代码

        $response = $next($request); // 将请求传递给下一个 Middleware 或应用程序

        // 在应用程序处理完请求之后执行的代码

        return $response;
    }
}

二、全局 Middleware 的应用

全局 Middleware 会应用于每一个进入应用程序的 HTTP 请求。它们通常用于执行一些通用的任务,例如:

  • 记录请求日志
  • 设置应用程序的 locale
  • 检查 CSRF 令牌

2.1 注册全局 Middleware

全局 Middleware 在 app/Http/Kernel.php 文件的 $middleware 数组中注册。

<?php

namespace AppHttp;

use IlluminateFoundationHttpKernel as HttpKernel;

class Kernel extends HttpKernel
{
    /**
     * The application's global HTTP middleware stack.
     *
     * These middleware are run during every request to your application.
     *
     * @var array<int, string>
     */
    protected $middleware = [
        AppHttpMiddlewareTrustProxies::class,
        FruitcakeCorsHandleCors::class,
        AppHttpMiddlewarePreventRequestsDuringMaintenance::class,
        IlluminateFoundationHttpMiddlewareValidatePostSize::class,
        AppHttpMiddlewareTrimStrings::class,
        IlluminateFoundationHttpMiddlewareConvertEmptyStringsToNull::class,
    ];

    // ...
}

2.2 示例:记录请求日志的全局 Middleware

假设我们需要记录每个请求的 URL 和 IP 地址。我们可以创建一个名为 LogRequest 的全局 Middleware。

<?php

namespace AppHttpMiddleware;

use Closure;
use IlluminateHttpRequest;
use IlluminateSupportFacadesLog;

class LogRequest
{
    /**
     * Handle an incoming request.
     *
     * @param  IlluminateHttpRequest  $request
     * @param  Closure(IlluminateHttpRequest): (IlluminateHttpResponse|IlluminateHttpRedirectResponse)  $next
     * @return IlluminateHttpResponse|IlluminateHttpRedirectResponse
     */
    public function handle(Request $request, Closure $next)
    {
        Log::info('Request URL: ' . $request->url());
        Log::info('Request IP: ' . $request->ip());

        return $next($request);
    }
}

然后,在 app/Http/Kernel.php 中注册这个 Middleware:

protected $middleware = [
    // ...
    AppHttpMiddlewareLogRequest::class,
];

现在,每个请求都会在日志文件中记录 URL 和 IP 地址。

2.3 全局 Middleware 的执行顺序

全局 Middleware 的执行顺序与它们在 $middleware 数组中的顺序一致。因此,在设计全局 Middleware 时,需要仔细考虑它们的执行顺序,以确保它们能够正确地协同工作。 例如,如果一个 Middleware 依赖于另一个 Middleware 设置的值,那么必须确保依赖的 Middleware 在它之前执行。

三、路由 Middleware 的应用

路由 Middleware 只应用于特定的路由或路由组。它们通常用于执行一些针对特定路由的身份验证、授权或数据验证。

3.1 注册路由 Middleware

路由 Middleware 在 app/Http/Kernel.php 文件的 $routeMiddleware 数组中注册。

<?php

namespace AppHttp;

use IlluminateFoundationHttpKernel as HttpKernel;

class Kernel extends HttpKernel
{
    // ...

    /**
     * The application's route middleware groups.
     *
     * @var array<string, array<int, string>>
     */
    protected $middlewareGroups = [
        'web' => [
            AppHttpMiddlewareEncryptCookies::class,
            IlluminateCookieMiddlewareAddQueuedCookiesToResponse::class,
            IlluminateSessionMiddlewareStartSession::class,
            IlluminateViewMiddlewareShareErrorsFromSession::class,
            AppHttpMiddlewareVerifyCsrfToken::class,
            IlluminateRoutingMiddlewareSubstituteBindings::class,
        ],

        'api' => [
            AppHttpMiddlewareEncryptCookies::class,
            IlluminateCookieMiddlewareAddQueuedCookiesToResponse::class,
            IlluminateSessionMiddlewareStartSession::class,
            IlluminateViewMiddlewareShareErrorsFromSession::class,
            'throttle:api',
            IlluminateRoutingMiddlewareSubstituteBindings::class,
        ],
    ];

    /**
     * The application's route middleware.
     *
     * These middleware may be assigned to groups or used individually.
     *
     * @var array<string, class-string|string>
     */
    protected $routeMiddleware = [
        'auth' => AppHttpMiddlewareAuthenticate::class,
        'auth.basic' => IlluminateAuthMiddlewareAuthenticateWithBasicAuth::class,
        'cache.headers' => IlluminateHttpMiddlewareSetCacheHeaders::class,
        'can' => IlluminateAuthMiddlewareAuthorize::class,
        'guest' => AppHttpMiddlewareRedirectIfAuthenticated::class,
        'signed' => IlluminateRoutingMiddlewareValidateSignature::class,
        'throttle' => IlluminateRoutingMiddlewareThrottleRequests::class,
        'verified' => IlluminateAuthMiddlewareEnsureEmailIsVerified::class,
    ];
}

在这个数组中,键是 Middleware 的别名,值是 Middleware 类的完全限定名称。

3.2 应用路由 Middleware

注册之后,可以在路由定义中使用 Middleware 的别名。可以通过以下几种方式应用路由 Middleware:

  • 单个路由:

    Route::get('/profile', [ProfileController::class, 'show'])->middleware('auth');
  • 路由组:

    Route::middleware(['auth'])->group(function () {
        Route::get('/profile', [ProfileController::class, 'show']);
        Route::post('/profile', [ProfileController::class, 'update']);
    });
  • 控制器构造函数:

    <?php
    
    namespace AppHttpControllers;
    
    use AppHttpControllersController;
    
    class ProfileController extends Controller
    {
        public function __construct()
        {
            $this->middleware('auth');
        }
    
        public function show()
        {
            // ...
        }
    
        public function update()
        {
            // ...
        }
    }

3.3 示例:验证用户权限的路由 Middleware

假设我们有一个 CheckRole Middleware,用于验证用户是否具有访问特定路由所需的角色。

<?php

namespace AppHttpMiddleware;

use Closure;
use IlluminateHttpRequest;
use IlluminateSupportFacadesAuth;

class CheckRole
{
    /**
     * Handle an incoming request.
     *
     * @param  IlluminateHttpRequest  $request
     * @param  Closure(IlluminateHttpRequest): (IlluminateHttpResponse|IlluminateHttpRedirectResponse)  $next
     * @param  string  $role
     * @return IlluminateHttpResponse|IlluminateHttpRedirectResponse
     */
    public function handle(Request $request, Closure $next, string $role)
    {
        if (!Auth::check()) {
            return redirect('login'); // 或者抛出异常
        }

        if (!Auth::user()->hasRole($role)) {
            abort(403, 'Unauthorized.'); // 或者返回其他错误页面
        }

        return $next($request);
    }
}

这个 Middleware 接受一个 $role 参数,用于指定允许访问该路由的角色。

首先,我们需要在 app/Http/Kernel.php 中注册这个 Middleware:

protected $routeMiddleware = [
    // ...
    'checkRole' => AppHttpMiddlewareCheckRole::class,
];

然后,可以在路由定义中使用这个 Middleware,并传递角色参数:

Route::get('/admin/dashboard', [AdminController::class, 'dashboard'])->middleware('checkRole:admin');

在这个例子中,只有具有 admin 角色的用户才能访问 /admin/dashboard 路由。

3.4 路由组 Middleware 的执行顺序

当多个 Middleware 应用于同一个路由组时,它们的执行顺序与它们在 $middlewareGroups 数组中定义的顺序一致。例如:

protected $middlewareGroups = [
    'web' => [
        AppHttpMiddlewareEncryptCookies::class,
        IlluminateCookieMiddlewareAddQueuedCookiesToResponse::class,
        IlluminateSessionMiddlewareStartSession::class,
        IlluminateViewMiddlewareShareErrorsFromSession::class,
        AppHttpMiddlewareVerifyCsrfToken::class,
        IlluminateRoutingMiddlewareSubstituteBindings::class,
    ],
];

在这个例子中,EncryptCookies Middleware 会在 AddQueuedCookiesToResponse Middleware 之前执行。

四、参数化 Middleware 的应用

参数化 Middleware 允许我们在路由定义中向 Middleware 传递参数,从而实现更加灵活和可配置的中间件行为。CheckRole就是一个参数化的middleware的例子。

4.1 实现参数化 Middleware

要实现参数化 Middleware,只需要在 handle 方法中定义额外的参数即可。这些参数将在路由定义中传递。

<?php

namespace AppHttpMiddleware;

use Closure;
use IlluminateHttpRequest;

class ParametrizedMiddleware
{
    /**
     * Handle an incoming request.
     *
     * @param  IlluminateHttpRequest  $request
     * @param  Closure(IlluminateHttpRequest): (IlluminateHttpResponse|IlluminateHttpRedirectResponse)  $next
     * @param  string  $parameter1
     * @param  int  $parameter2
     * @return IlluminateHttpResponse|IlluminateHttpRedirectResponse
     */
    public function handle(Request $request, Closure $next, string $parameter1, int $parameter2)
    {
        // 使用 $parameter1 和 $parameter2 进行处理

        return $next($request);
    }
}

4.2 使用参数化 Middleware

在路由定义中使用参数化 Middleware 时,需要将参数通过冒号分隔传递给 Middleware 的别名。

Route::get('/example', [ExampleController::class, 'index'])->middleware('parametrized:value1,123');

在这个例子中,value1123 将分别作为 $parameter1$parameter2 传递给 ParametrizedMiddlewarehandle 方法。

4.3 示例:基于 IP 地址的访问控制

假设我们需要创建一个 Middleware,用于限制特定 IP 地址的访问。

<?php

namespace AppHttpMiddleware;

use Closure;
use IlluminateHttpRequest;

class AllowOnlyFromIp
{
    /**
     * Handle an incoming request.
     *
     * @param  IlluminateHttpRequest  $request
     * @param  Closure(IlluminateHttpRequest): (IlluminateHttpResponse|IlluminateHttpRedirectResponse)  $next
     * @param  string  $allowedIp
     * @return IlluminateHttpResponse|IlluminateHttpRedirectResponse
     */
    public function handle(Request $request, Closure $next, string $allowedIp)
    {
        if ($request->ip() !== $allowedIp) {
            abort(403, 'Access denied.');
        }

        return $next($request);
    }
}

首先,我们需要在 app/Http/Kernel.php 中注册这个 Middleware:

protected $routeMiddleware = [
    // ...
    'allowIp' => AppHttpMiddlewareAllowOnlyFromIp::class,
];

然后,可以在路由定义中使用这个 Middleware,并传递允许的 IP 地址:

Route::get('/restricted', [RestrictedController::class, 'index'])->middleware('allowIp:127.0.0.1');

在这个例子中,只有来自 127.0.0.1 的请求才能访问 /restricted 路由。

五、总结

今天我们学习了 Laravel Middleware 的高级应用,包括全局 Middleware、路由 Middleware 和参数化 Middleware。

  • 全局 Middleware 用于处理所有请求,适用于通用任务。
  • 路由 Middleware 用于处理特定路由或路由组的请求,适用于身份验证、授权等场景。
  • 参数化 Middleware 允许我们在路由定义中向 Middleware 传递参数,从而实现更加灵活和可配置的中间件行为。

通过合理地使用这些 Middleware,我们可以构建更加健壮、安全和可维护的 Laravel 应用程序。

Middleware 的设计原则

Middleware 的设计应该遵循以下原则:

  • 单一职责原则: 每个 Middleware 应该只负责一个特定的任务。
  • 可配置性: Middleware 应该易于配置,以便在不同的环境中使用。
  • 可测试性: Middleware 应该易于测试,以确保其正确性。

灵活运用 Middleware ,让应用更健壮

掌握 Middleware 的高级应用,能够帮助我们更好地控制请求的处理流程,提高应用程序的安全性、可维护性和可扩展性。希望今天的讲解能够对你有所帮助。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注