第一次看到laravel对中间件实现的流程估计都有点懵逼,主要是作者封装太多,对于这种过度封装的框架只能对其代码进行简化才能更容易的看清其套路。
下面我们先写三个简单的中间件类,A B C 中间件都继承自X基类:
class A extends X{} class B extends X{} class C extends X{} abstract class X{ public function handle($request, Closure $next){ //do something echo get_class($this); return $next($request); } }
然后是Request类,因为内部所有功能都没用到所以暂时就直接一个空类就好了
class Request{ //处理用户输入类 }
下面是Response类,只用到构造函数及send方法,send在laravel里是负责输出包括头部信息在内的所有内容到浏览器,这里我们也进行简化
class Response{ //数据输出 private $content; public function __construct($content) { $this->content = (string)$content; } public function send() { echo $this->content; } }
再定义一个简单的路由类,也是简化版直接执行控制器
class Route{ //返回控制器执行结果,一般是个content类或者字符串 public function run(){ return (new TestController())->index(); } }
下面定义一个Content类及Test控制器
class Content{ public function __toString() { return '测试内容';//这里本来是进行模板文件的渲染,这里直接返回字符串 } } class TestController{ public function index(){ return new Content(); } }
好了,重点来了,以下是laravel实现中间件的简化版,其实主要还是靠array_reduce这个内置函数,这个函数会根据middleware数组进行递归调用,每一次递归带入的$stack参数为上次的匿名函数,从宏观看就是匿名函数的多层包裹,最后执行的匿名函数是$destination,这个匿名函数返回的是Response的实例,最后执行send方法,整个流程就结束了。
class PipeLine{ private $passable; private $method = 'handle'; public function send($passable) { $this->passable = $passable; return $this; } public function then(Closure $destination) { $middlewares = ['A','B','C']; $pipeline = array_reduce(array_reverse($middlewares),$this->carry(),$destination); return $pipeline($this->passable); } public function carry(){ return function($stack,$pipe){ return function($passable) use ($stack,$pipe){ $slice = $this->baseCarry(); $callable = $slice($stack, $pipe);//第一次调用的时候,$pipe为反转后的中间件数组的最后一个,也就是没反转前的第一个中间件 return $callable($passable); }; }; } public function baseCarry(){ return function($stack,$pipe){ return function($passable) use ($stack,$pipe){ if(!is_callable($pipe)){ $cls = new $pipe(); } return $cls->{$this->method}($passable,$stack);//每次会调用中间件实例的handle方法,当执行最后一个中间件的handle方法后会调用destination匿名函数 }; }; } }
下面是简化版调用
function prepareResponse($request,$response){ return new Response($response); } function toResponse() { $request = new Request(); return (new PipeLine()) ->send($request) ->then(function($request){ return prepareResponse($request,(new Route())->run()) ; }); } $response = toResponse(); $response->send();
输出结果:ABC测试内容