中间件在 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