laravel框架中间件原理
都说laravel框架是优雅的,通过命名直接了解大概功能,所以我对其的实现原理也产生了兴趣。其中中间件是框架中非常常用的,使用也很简单,需要扩展+配置即可,接下来我介绍一下这个原理,有不当的地方欢迎各位网友拍砖
从单一入口开始追踪,不难找到框架核心类Illuminate\Foundation\Http\Kernel,核心类处理请求的方法为handle,handle方法中请求交给sendRequestThroughRouter($request) 方法处理
1 protected function sendRequestThroughRouter($request) 2 { 3 $this->app->instance('request', $request); 4 5 Facade::clearResolvedInstance('request'); 6 7 $this->bootstrap(); 8 9 return (new Pipeline($this->app)) 10 ->send($request) 11 ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware) 12 ->then($this->dispatchToRouter()); 13 }
上面的代码中,返回的代码中很好翻译,就是返回一个管道列,这个管道是通过中间件,路由,然后一级一级发送请求。那如何实现,且看Illuminate\Pipeline
public function send($passable) { $this->passable = $passable; return $this; } public function through($pipes) { $this->pipes = is_array($pipes) ? $pipes : func_get_args(); return $this; }
以上代码是非常容易理解的
public function then(Closure $destination) { $pipeline = array_reduce( array_reverse($this->pipes), $this->carry(), $this->prepareDestination($destination) ); return $pipeline($this->passable); }
以上方法要了解array_reduce 方法通过迭代数组的元素(将上次在匿名函数中返回的结果作为第二次该匿名函数的第一个参数,函数第二个参数为数组的中元素,理解这个很重要!)这个函数可以查手册,这里不再复述.
array_reverse 用来倒置中间件,如$this->pipes 存储为中间件1,中间件2,中间件3,倒置后为中间件3,中间2,中间件1。
这里的匿名函数就是指$this->carry();
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)) { list($name, $parameters) = $this->parsePipeString($pipe); $pipe = $this->getContainer()->make($name); $parameters = array_merge([$passable, $stack], $parameters); } else { $parameters = [$passable, $stack]; } return method_exists($pipe, $this->method) ? $pipe->{$this->method}(...$parameters) : $pipe(...$parameters); }; }; }
到这里有很多return function .看的人很晕,这也是设计妙的地方 。
第一个return function($stack,$pipe),是指以上array_reduce的第二个参数即匿名函数。
当array_reduce 第一次迭代时,匿名函数中参数$stack 为$this->prepareDestination($destination),$pipe 为中间3,处理的结果为第二个return function($passable)use($stack,中间件3){}(这样写不合法,为了容易理解暂且这样写吧)。
到这里还记得上面说过的匿名函数的第一个参数 是上次处理的结果么?所以第二次迭代,匿名函数的参数$stack为function($passable)use($this->prepareDestination($destination),中间件3){},$pipe为中间件2,处理的结果为return function($passable)use($stack,中间件2){}.
以此推理到中间件1.所以array_reduce返回的结果就是function($passable)use(function($passable)use(function($passable)use($stack,中间件3){},中间件2){},中间件1);
执行时依次是中间件1,中间件2,中间件3,路由。那怎么会这样顺序执行呢?以上可以看出先执行中间1为参数的匿名函数,这个匿名函数的实现,reuturn $pipe->{$this->method}(...$parameters) 不难看出$this->method为handle,因为每个中间件都有一个handle方法。$parameters 不难看出为第一个参数为$passable,第二个参数$stack。如\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode
namespace Illuminate\Foundation\Http\Middleware; class CheckForMaintenanceMode { protected $app; public function __construct(Application $app) { $this->app = $app; } public function handle($request, Closure $next) { if ($this->app->isDownForMaintenance()) { $data = json_decode(file_get_contents($this->app->storagePath().'/framework/down'), true); throw new MaintenanceModeException($data['time'], $data['retry'], $data['message']); } return $next($request); } }
如handle中request为请求,第二个参数$next 为function($passable)use($stack,中间件2){} 用来执行下一个中间件。依次类推。。。当然如果某个中间件执行失败可以不在执行下一个中间件。
写到此,中间件原理说完了,是否挺晕的呢?哈哈哈,反正我当初看这部分是非常晕的,常常搞得头大,其中对一些不懂或似懂非懂的知识一定要搞清楚呢。以此与各位共勉!
浙公网安备 33010602011771号