打开入口文件index.php,我们发现,laravel在通过Application对象解析(make)出
Http Kernel对象后,会调用kernel对象的handle方法,我们的中间件Middleware就是在这里去调用的。
<?php
//index.php 入口文件
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
$response = $kernel->handle(
$request = Illuminate\Http\Request::capture()
);
//Http Kernel.class 类文件
//handle方法
public function handle($request)
{
try {
$request->enableHttpMethodParameterOverride();
//将$request对象穿透路由
$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 RequestHandled($request, $response)
);
return $response;
}
//sendRequestThroughRouter方法
protected function sendRequestThroughRouter($request)
{
//将request对象放进Application对象的$instances属性中
$this->app->instance('request', $request);
Facade::clearResolvedInstance('request');
//先启动服务:,调用这些服务的bootstrap方法
/**
* \Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class, 加载环境变量
\Illuminate\Foundation\Bootstrap\LoadConfiguration::class, 加载配置信息
\Illuminate\Foundation\Bootstrap\HandleExceptions::class, 处理异常
\Illuminate\Foundation\Bootstrap\RegisterFacades::class, 注册门面
\Illuminate\Foundation\Bootstrap\RegisterProviders::class, 调用服务提供者的register方法,一般是向服务容器注册服务
\Illuminate\Foundation\Bootstrap\BootProviders::class, 调用服务提供者的boot方法
*/
$this->bootstrap();
//这里就是中间件的调用流转,会将$request对象通过管道的方式一层层(像剥洋葱一样)传递下去执行
return (new Pipeline($this->app))
->send($request)
->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
->then($this->dispatchToRouter());
}
Pipeline对象就是我们今天研究的对象,看上面我们调用了Pipeline对象的三个方法
方法一:send(),这里我们会将$request传进去给Pipeline对象的$passable属性;
方法二:through(),这里我们将http kernel对象中的$middleware属性传进去给Pipeline对象的$pipes属性;
方法三:then(),我们的重点研究方法,是将从路由中解析出的控制器方法闭包形式传进去给$destination形参;
我们打开then方法,看到如下的代码
protected function carry()
{
return function ($stack, $pipe) {
return function ($passable) use ($stack, $pipe) {
try {
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];
}
$carry = method_exists($pipe, $this->method)
? $pipe->{$this->method}(...$parameters)
: $pipe(...$parameters);
return $this->handleCarry($carry);
} catch (Exception $e) {
return $this->handleException($passable, $e);
} catch (Throwable $e) {
return $this->handleException($passable, new FatalThrowableError($e));
}
};
};
}
public function then(Closure $destination)
{
$pipeline = array_reduce(
array_reverse($this->pipes()), $this->carry(), $this->prepareDestination($destination)
);
return $pipeline($this->passable);
}
这段代码的大致意思,先将$pipes数组(也就是传进来的$middleware)中的元素反转倒序array_reverse一下
这里有个比较陌生的array_reduce方法,这个方法有三个形参,作用是循环参数1中的$pipes元素,将第一个元素作为参数2{调用carry()}返回的回调方法的$pipe参数传递进去,而$destination就是传递给回调方法的$stack参数,这就完成了第一个元素的调用,生成一个闭包callback1,以此类推,第二个元素作为参数2{调用carry()}返回的回调方法的$pipe参数传递进去,而上一次处理第一个元素生成的callback1作为传递给回调方法的$stack参数。最终会生成一个层层递进像洋葱一样的回调函数,最后调用这个洋葱回调函数执行每个中间件的handle方法,来让$request对象在中间件中流转处理。
可能上面说了一大段会被绕晕,我这里从网上摘抄了一个简单点的例子,加以自己的理解放出来给大家去研究。
<?php
//laravel中间件的实现原理简化版:
interface Middleware
{
public static function handle($request,\Closure $next);
}
class CSRFToken implements Middleware
{
public static function handle($request,\Closure $next)
{
echo $request.'处理token';
echo "\n";
$next($request);//这里执行包含调用下一个中间件的handle方法的回调,将对象$request传进去
}
}
class SetSession implements Middleware
{
public static function handle($request,\Closure $next)
{
echo $request.'处理session';
echo "\n";
$next($request);//这里执行带有调用下一个中间件的handle方法的回调,将对象$request传进去
}
}
class CommonOrder implements Middleware
{
public static function handle($request,\Closure $next)
{
echo $request.'处理order下单';
echo "\n";
$next($request);//这里已经到了最后一个中间件,这里表示调用$destination回调函数,将对象$request传进去
}
}
$pipes = [
'CSRFToken',
'SetSession',
'CommonOrder',
];
$destination = function($passable){
echo '最终执行控制器方法';//类似:CommonOrderController::update($passable)
};
function prepareDestination(Closure $destination)
{
//这里是最后一个中间件执行的回调函数,里面包含执行控制器方法并返回响应
return function ($passable) use ($destination) {
return $destination($passable);
};
}
function carry()
{
return function ($stack, $pipe) {
//第一次执行的$stack是$destination 回调函数,$pipe是数组$pipes的第一个元素'CommonOrder'
//每执行一次返回的这个callback就作为下一次执行carry()返回的回调函数的$stack参数,数组的下一个元素'SetSession'作为$pipe参数,以此类推
return function ($passable) use ($stack, $pipe) {
return $pipe::handle($passable,$stack);
};
};
}
$pipeline = array_reduce(array_reverse($pipes), carry(),prepareDestination($destination));
print_r($pipeline);//这里的pipeline就是跟洋葱一样带有层层递进回调函数的回调函数
$passable = 'request对象';
$pipeline($passable);//执行这个跟洋葱一样的回调函数,把$request对象传进去
贴出上面跟洋葱一样的回调函数伪代码和最终执行结果
//伪代码:第一次形成的闭包函数
function ($passable) {
return CommonOrder::handle($passable,function ($passable) use ($destination) {
return $destination($passable);
});
}
//伪代码:第二次形成的闭包函数
function ($passable) {
return SetSession::handle($passable,function ($passable) {
return CommonOrder::handle($passable,function ($passable) use ($destination) {
return $destination($passable);
});
});
}
//伪代码:第三次形成的闭包函数
function ($passable) {
return CSRFToken::handle($passable,function ($passable) {
return SetSession::handle($passable,function ($passable) {
return CommonOrder::handle($passable,function ($passable) use ($destination) {
return $destination($passable);
});
});
});
}
//request对象处理token
//request对象处理session
//request对象处理order下单
//最终执行控制器方法%
这就是Laravel实现中间件的大致原理啦,记录到这里学习用。