Laravel路由中间件的实现原理

50 篇文章 2 订阅

Laravel 中间件提供了一种方便的机制来过滤进入应用的 HTTP 请求。例如,Laravel 内置了一个中间件来验证用户的身份认证。如果用户没有通过身份认证,中间件会将用户重定向到登录界面。但是,如果用户被认证,中间件将允许该请求进一步进入该应用。

当然,除了身份认证以外,还可以编写另外的中间件来执行各种任务。例如:CORS 中间件可以负责为所有离开应用的响应添加合适的头部信息;日志中间件可以记录所有传入应用的请求。

Laravel 自带了一些中间件,包括身份验证、CSRF 保护等。所有这些中间件都位于 app/Http/Middleware 目录。

简单来说就是请求在不去修改自身的逻辑,通过中间件扩展或者处理一些功能。

1、先来了解一下 array_reduce 函数
地址:https://blog.csdn.net/raoxiaoya/article/details/103447483

2、源码解读
index.php中

$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);

$response = $kernel->handle(
    $request = Illuminate\Http\Request::capture()
);

$response->send();

$kernel->terminate($request, $response);

然后
Illuminate\Foundation\Http\Kernel 的handle方法

public function handle($request)
{
    try {
        $request->enableHttpMethodParameterOverride();

        $response = $this->sendRequestThroughRouter($request);
    } catch (Exception $e) {
        $this->reportException($e);

        $response = $this->renderException($request, $e);
    } catch (Throwable $e) {
        $this->reportException($e = new FatalThrowableError($e));

        $response = $this->renderException($request, $e);
    }

    $this->app['events']->dispatch(
        new Events\RequestHandled($request, $response)
    );

    return $response;
}

protected function sendRequestThroughRouter($request)
{
    $this->app->instance('request', $request);

    Facade::clearResolvedInstance('request');

    $this->bootstrap();

    return (new Pipeline($this->app))
                ->send($request)
                ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
                ->then($this->dispatchToRouter());
}
// 通过管道之后进入路由,控制器
protected function dispatchToRouter()
{
    return function ($request) {
        $this->app->instance('request', $request);

        return $this->router->dispatch($request);
    };
}

然后是 Illuminate\Pipeline\Pipeline

send 方法设置通过管道的数据 即request
through 方法设置要通过的管道数组
then 方法是执行,传入的参数是一个闭包,也就是如果顺利通过管道后进入的路由层。

public function then(Closure $destination)
{
    $pipeline = array_reduce(
        array_reverse($this->pipes), $this->carry(), $this->prepareDestination($destination)
    );

    // 执行闭包,并传入一个参数,也就是request对象
    return $pipeline($this->passable);
}

protected function prepareDestination(Closure $destination)
{
    return function ($passable) use ($destination) {
        return $destination($passable);
    };
}

根据 array_reduce(元素数组, 回调函数, 初始化值) 的了解,如果传入的是闭包嵌套,那么最终解包后的执行顺序是反的,也就是说最里层的闭包最后执行,而在封装的时候,第一个是在最里面的,最后一个是最外面的,所以要使用array_reverse来反转middlewares,并且最后的路由层反而应该设置在初始化的位置.

那么问题来了,我们定义的路由中间件都是单独的类,实现了 public function handle($request, Closure $next) 方法,如何将其封装为闭包呢?实际上在外层套一个闭包函数就可以了

return function ($passable) use ($stack, $pipe) {
	return (new $pipe)->handle($passable, $stack);
}

为了实现在carry方法中统一的调用方式,将 $destination 在嵌套一层闭包,也就是 prepareDestination 方法的作用,只需要再次 use 自己就行。实际上不加这一层也没毛病。

protected function carry()
{
    return function ($stack, $pipe) {
        return function ($passable) use ($stack, $pipe) {
            if (is_callable($pipe)) {
                return $pipe($passable, $stack);
            } elseif (! is_object($pipe)) {
                [$name, $parameters] = $this->parsePipeString($pipe);
                $pipe = $this->getContainer()->make($name);
                $parameters = array_merge([$passable, $stack], $parameters);
            } else {
                $parameters = [$passable, $stack];
            }

            $response = method_exists($pipe, $this->method)
                            ? $pipe->{$this->method}(...$parameters)
                            : $pipe(...$parameters);

            return $response instanceof Responsable
                        ? $response->toResponse($this->container->make(Request::class))
                        : $response;
        };
    };
}

要注意,array_reduce 的作用只是形成一个递归闭包,要使其能够一层层执行下去,需要 $pipeline($this->passable) 触发,然后还需要内部建立关联关系,即 $next($request) ,而刚好最里层的闭包不需要 next,设计得还算很巧妙。由这种嵌套的方式注定了你不能设置太多的中间件,因为层次越深性能将急剧下降。

一个示例

<?php
/**
 * ----------------------------------------------------------
 * date: 2019/12/8 14:39
 * ----------------------------------------------------------
 * author: Raoxiaoya
 * ----------------------------------------------------------
 * describe:
 * ----------------------------------------------------------
 */

class VerfiyCsrfToken
{
    public function handle($request, Closure $next)
    {
        print_r($request);
        $request['token'] = 'xxxxxxxxxxx';
        $response         = $next($request);
        return $response;
    }
}

class VerfiyAuth
{
    public function handle($request, Closure $next)
    {
        print_r($request);
        $request['auth'] = 'aaaaaaaaaaa';
        $response        = $next($request);
        return $response;
    }
}

class SetCookieInfo
{
    public function handle($request, Closure $next)
    {
        print_r($request);
        $request['cookie'] = 'ccccccccccccc';
        $response = $next($request);
        return $response;
    }
}

$middlewareArr = [
    'VerfiyCsrfToken',
    'VerfiyAuth',
    'SetCookieInfo',
];

$request = [];

$destination = function ($request) {
    print_r($request);
    return '通过管道之后';
};
$handle = function ($passable) use ($destination) {
    return $destination($passable);
};

$pipeline = array_reduce(
    array_reverse($middlewareArr),
    function ($stack, $pipe) {
        return function ($passable) use ($stack, $pipe) {
            if (is_callable($pipe)) {
                return $pipe($passable, $stack);
            } elseif (! is_object($pipe)) {
                $pipe = new $pipe();
            }

            $response = $pipe->handle($passable, $stack);

            return $response;
        };
    },
    $handle
);

print_r($pipeline);
var_dump($pipeline($request));

最终得到的大闭包
在这里插入图片描述
执行结果:
在这里插入图片描述

我们打印出解包过程:
在这里插入图片描述
第一次解包时的打印
在这里插入图片描述
第二次解包时打印
在这里插入图片描述
第三次解包时打印
在这里插入图片描述
只打印了三次,那还有一次呢?因为经过这个回调函数的只有我们定义的三个中间件,最后一个不经过这里,所以不会在这里执行。这样的话 is_callable 也是多余的。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Laravel 中,注册中间件需要遵循以下步骤: 1. 创建中间件类 首先,你需要创建一个中间件类。可以使用 Artisan 命令 `php artisan make:middleware <middleware-name>` 来创建一个新的中间件类。 例如,如果要创建一个名为 `CheckAge` 的中间件,可以运行以下命令: ``` php artisan make:middleware CheckAge ``` 该命令将在 `app/Http/Middleware` 目录中创建一个名为 `CheckAge.php` 的中间件类。 2. 编写中间件逻辑 在中间件类中,你需要实现 `handle` 方法来定义中间件要执行的逻辑。`handle` 方法接收两个参数:`$request` 和 `$next`。 `$request` 是当前请求的实例,而 `$next` 是下一个中间件由的闭包。 例如,以下是一个简单的 `CheckAge` 中间件,用于检查用户的年龄是否大于 18 岁: ```php <?php namespace App\Http\Middleware; use Closure; class CheckAge { public function handle($request, Closure $next) { if ($request->age < 18) { return redirect('home'); } return $next($request); } } ``` 在上面的例子中,如果用户的年龄小于 18 岁,则中间件将重定向到 `home` 由,否则将继续执行下一个中间件由。 3. 注册中间件 完成中间件类后,你需要将其注册到应用程序中。可以在 `app/Http/Kernel.php` 文件中找到中间件注册表。 该文件包含了一个 `$middleware` 数组,其中包含了全局中间件。你也可以将中间件分组并定义在 `$middlewareGroups` 数组中,或者将它们定义在单独的 `$routeMiddleware` 数组中,并通过中间件来使用它们。 例如,以下是一个将 `CheckAge` 中间件注册为全局中间件的示例: ```php protected $middleware = [ \App\Http\Middleware\CheckAge::class, ]; ``` 或者,你可以将其注册为中间件: ```php protected $routeMiddleware = [ 'checkAge' => \App\Http\Middleware\CheckAge::class, ]; ``` 现在,你可以在由中使用 `checkAge` 中间件来检查用户的年龄: ```php Route::get('profile', function () { // })->middleware('checkAge'); ``` 或者,你可以将其添加到控制器的构造函数中: ```php public function __construct() { $this->middleware('checkAge'); } ``` 以上就是 Laravel 中注册中间件的步骤。希望对你有所帮助!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值