前言
对于没有登录的用户重定向到登录页,否则正常请求,想一下对于这个简单的需求你会怎么设计,之前我都是写一个基类,在基类的构造方法中判断是否登录,然后所有的类都继承这个基类,这样写有两个明显的问题1.对于一些不需要登录验证的请求,就得重写构造方法。2.随着这种需求越来越多比如传入的post参数大小的需要限制,这样基类的构造方法会越来越庞大,越来越难维护。(在某一天你就会觉得我怎么会写出这么恶心的代码。。。。)还好laravel提供了中间件。第一次接触laravel时,就被他的中间件深深吸引,可以很方便的给请求加一些额外的处理。今天就来膜拜一下这个神奇的功能是如何实现的。
laravel中间件的执行
中间件是通过pipeline这个管道类执行的(其实路由也是在这里执行的),其中send和through方法只是设置了属性,真正的执行就是then方法了,这个then方法真的是。。。。
<?php
protected function sendRequestThroughRouter($request)
{
$middleware = [
\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
\App\Http\Middleware\Cors::class,
];
$response = function ($request) {
$this->app->instance('request', $request);
return $this->router->dispatch($request);
};
return (new Pipeline($this->app))
->send($request)
->through($this->middleware)
->then($response);
}
then方法中用到了array_reduce函数,
class 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)
{
$inital = function ($passable) use($destination) {
return $destination($passable);
}
//这里的array_reverse是为了中间件执行时的顺序和定义时的顺序一致,这里用到了栈的先进后出,等会我们会用一个小实例来说明这个。
$pipeline = array_reduce(
array_reverse($this->pipes), $this->carry(), $inital
);
return $pipeline($this->passable);
}
public function carry()
{
return function ($stack, $pipe) {
return function ($passable) use ($stack, $pipe) {
$slice = this->carry2();
$callable = $slice($stack, $pipe);
return $callable($passable);
};
};
}
public function carry2()
{
return function ($stack, $pipe) {
return function ($passable) use ($stack, $pipe) {
[$name, $parameters] = $this->parsePipeString($pipe); //获取中间件名子和参数
$pipe = $this->getContainer()->make($name); //获取中间件对象
$parameters = array_merge([$passable, $stack], $parameters);//组合参数,这个$passable就是我们中间件中平时接受的$request,$stack就是接受的$next
$response = method_exists($pipe, 'handle') //如果中间件中存在handel方法就执行handel,如果没有就找中间件中的__invoke方法。
? $pipe->{$this->method}(...$parameters)
: $pipe(...$parameters);
return $response instanceof Responsable
? $response->toResponse($this->getContainer()->make(Request::class))
: $response;
};
};
}
}
一个简单的例子
在then方法中,执行完array_reduce这个方法的返回值是一个回调函数,打印结果如下图,注意看经过array_reduce之后最外层的回调函数中的static属性中的item是Test2,数组的最后一个,就是在array_reduce的时候把数组元素递归生成回调,这也就是为什么laravel中要用array_reverse反转一次。

执行$arr()的过程,($arr就是红色标出的回调函数)首先调用carry中的第二层回调函数,$this->carry2(),调用了carry2中的最外层的回调函数,$test($carry,$item)()执行carry2中的第二层的回调,其中$item是Test2,$carry是第下图中蓝色标出的回调函数,即执行了Test2类中的handel输出‘Test2’,然后执行蓝色标出的回调,会走到Test1的handel,执行黄色标出的回调,Test的handel,执行绿色标出的回调即
function () {
return 'prepareDestination';
};输出Test,Test1,33至此执行完毕。因为表达能力有限说的可能不是很明白建议大家借助xdebug执行这个小demo可能会有更深的理解。
<?php
class Pipine{
public function then()
{
$a = ['test', 'test1','Test2'];
$prepareDestination = function () {
return 'prepareDestination';
};
$arr = array_reduce($a, $this->carry(), $prepareDestination);
return $arr();
}
public function carry()
{
return function($carry, $item) {
return function () use($carry, $item){
$test = $this->carry2();
return $test($carry, $item)();
};
};
}
public function carry2()
{
return function($carry, $item) {
return function () use($carry, $item){
$tmp = (new $item)->handel($carry);
return $tmp;
};
};
}
}
class Test{
public function handel($carry) {
$response = $carry();
echo "Test<br/>";
return $carry();
}
}
class Test1 {
public function handel($carry) {
$respnse = $carry();
echo "Test1<br/>";
return 22;
}
}
class Test2 {
public function handel($carry) {
echo "Test2<br/>";
$carry();
return 33;
}
}
$pipine = new Pipine;
var_dump($pipine->then());
// Test2
// Test
// Test1
// 33
小demo的图解

网友评论