Home » Code » Laravel 中间件原理

Laravel 中间件原理

中间件在 Laravel 中有广泛的应用,本身也自带了很多有用的中间件,比如维护模式开关/ csrf token 验证/接口频率控制等等。我们也可以很方便的添加自定义的中间件来实现更多的功能。其实整个 Laravel 的生命周期,基本可以是看作是下边这个图:接收请求 Request -> 中间件处理 -> 返回响应 Response:

它是怎么实现的呢?主要原理是 array_reduce() 函数以及闭包的使用。

首先我们简单定义一个响应类,当我们在某个中间件中返回一个 Response 的实例,就意味着要停下来了,不要再继续执行其他中间件了。

class Response
{
    private $content;

    public function __construct($content)
    {
        $this->content = $content;
    }

    public function __toString()
    {
        return $this->content;
    }
}

中间件需要实现以下接口,必须包含一个 handle() 方法:

interface Middleware
{
    public function handle($request, \Closure $next);
}

我们随便定义三个,只做简单输出:

class MiddlewareA implements Middleware
{
    public function handle ($request, $next)
    {
        echo "运行了中间件:" . __CLASS__ . "\n";
        return $next($request);
    }
}

class MiddlewareB implements Middleware
{
    public function handle ($request, $next)
    {
        echo "运行了中间件:" . __CLASS__ . "\n";
        // return new Response("something wrong...");
        return $next($request);
    }
}

class MiddlewareC implements Middleware
{
    public function handle ($request, $next)
    {
        echo "运行了中间件:" . __CLASS__ . "\n";

        return $next($request);
    }
}

下面的 carry() 方法,是实现中间件的核心方法,它是我们使用 array_reduce() 时的参数二——回调函数,每次迭代都返回一个闭包,最终迭代完成后的结果是一个包含所有中间件的大闭包。

//把多个中间件合并成一个闭包
function carry($carry, $middleware) {
    return function ($request) use ($carry, $middleware) {
        $log = "current middleware: $middleware";
        // var_dump($log, $carry);
        $obj = new $middleware;
        $result = $obj->handle($request, $carry);
        // var_dump('carry', $result);
        if ($result instanceof \Response) {
            exit($result);
        }
        return $result;
    };
}

执行最关键的一步时,创建一个闭包用于 array_reduce() 的参数三———初始值,它会从路由获得最终的响应结果。

function dispatchToRouter() {
    return function ($request) {
        $result = "运行路由(一般就是控制器路由)后的结果 \n";
        echo $result;
        return $result;
    };
}

最关键的一步:

$middlewares = ['MiddlewareA', 'MiddlewareB', 'MiddlewareC'];
$result = array_reduce($middlewares, 'carry', dispatchToRouter());
var_dump($result);
var_dump('end', $result($_REQUEST));

最终输出如下:

object(Closure)#4 (2) {
  ["static"]=>
  array(2) {
    ["carry"]=>
    object(Closure)#3 (2) {
      ["static"]=>
      array(2) {
        ["carry"]=>
        object(Closure)#2 (2) {
          ["static"]=>
          array(2) {
            ["carry"]=>
            object(Closure)#1 (1) {
              ["parameter"]=>
              array(1) {
                ["$request"]=>
                string(10) "<required>"
              }
            }
            ["middleware"]=>
            string(11) "MiddlewareA"
          }
          ["parameter"]=>
          array(1) {
            ["$request"]=>
            string(10) "<required>"
          }
        }
        ["middleware"]=>
        string(11) "MiddlewareB"
      }
      ["parameter"]=>
      array(1) {
        ["$request"]=>
        string(10) "<required>"
      }
    }
    ["middleware"]=>
    string(11) "MiddlewareC"
  }
  ["parameter"]=>
  array(1) {
    ["$request"]=>
    string(10) "<required>"
  }
}
运行了中间件:MiddlewareC
运行了中间件:MiddlewareB
运行了中间件:MiddlewareA
运行路由(一般就是控制器路由)后的结果 
string(3) "end"
string(59) "运行路由(一般就是控制器路由)后的结果 
"

可以看到,最终执行大闭包时,是先执行各个中间件,再执行初始值运行路由那一段——这正符合我们的需求——这些全局中间件都是在请求到达控制器之前执行的,而且是从外往内往肉执行的,因此实际在输入时要 array_reverse() 一下才能保证各中间件的执行顺序跟输入的一致。

如果打开 MiddlewareB 的返回一个 Response 实例,那么输出将会是:

...
运行了中间件:MiddlewareC
运行了中间件:MiddlewareB
something wrong...

以上是模拟,实际在 Laravel 中封装成了一个管理类 Pipeline ,使得最终的写法比较符合它之前的 slogon:

(new Pipeline())->send($request)->through($middlewares)->then(dispatchToRouter());

dd

Leave a Reply

Your email address will not be published. Required fields are marked *

*

Time limit is exhausted. Please reload CAPTCHA.