Pipeline类中function then()的实现原理
// 文件 vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php | |
// 管道类 | |
class Pipeline implements PipelineContract | |
{ | |
public function then(Closure $destination) | |
{ | |
$pipeline = array_reduce( | |
array_reverse($this->pipes()), $this->carry(), $this->prepareDestination($destination) | |
); | |
return $pipeline($this->passable); | |
} | |
protected function prepareDestination(Closure $destination) | |
{ | |
return function ($passable) use ($destination) { | |
try { | |
return $destination($passable); | |
} catch (Throwable $e) { | |
return $this->handleException($passable, $e); | |
} | |
}; | |
} | |
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 (Throwable $e) { | |
return $this->handleException($passable, $e); | |
} | |
}; | |
}; | |
} | |
} |
要理解Pipeline类中then实现中间件的原理需要着重了解一下重点:
- 内置函数:array_reduce()
- 闭包类型:Closure
- 内置函数:array_reverse()
下面手把手带你实现下面的功能:
then()功能分析实现
我们可以通过dd($this->pipes())打印发现,$this->pipes()是一个中间件的数组;
$this->carry() 和 $destination都是一个闭包
步骤一 :
那么我们来简单模仿一下写个简单的方法来先了解下array_reduce()
先写一个简单的控制器:
// 文件 /routes/web.php | |
Route::get('/','TestController@index'); | |
// 文件 /app/Http/Controllers/TestController.php | |
public function index() | |
{ | |
$funcA = function () { | |
echo "funcA <br/>"; | |
}; | |
$funcB = function () { | |
echo "funcB <br/>"; | |
}; | |
$funcC = function () { | |
echo "funcC <br/>"; | |
}; | |
$pipes = [$funcA, $funcB, $funcC]; | |
// $carry 是上一个函数的返回值; 若一开是第三个参数为空时 则为null | |
// $item 数组的元素值 | |
// 此处我们不考虑$carry 我们让他每次都运行 闭包$item() | |
array_reduce($pipes, function ($carry, $item) { | |
$item(); | |
}); | |
} |
结果:
步骤二
现在我们再修改一下,此时我们闭包内不执行$item,而是作为$carry值返回给下一次循环作为参数调用;并且加入第三个参数,为$carry赋一个默认值:
public function index() | |
{ | |
... ... // 前面内容不变 | |
// 第三个参数为一个 字符串 , 这个值会作为第一次调用时,$carry的值 | |
$finalFunc = array_reduce($pipes, function ($carry, $item) { | |
// 如果是字符串则直接打印 | |
if (is_string($carry)) { | |
echo $carry; | |
} elseif ($carry instanceof \Closure) { | |
// 如果是闭包则运行 | |
$carry(); | |
} | |
// 向下一个调用的$carry返回当前的闭包$item; | |
return $item; | |
}, "Init<br/>"); | |
// 三次循环的参数与返回如下: | |
// 第一次参数:$carry= "Init<br/>"; $item=closure funcA() 返回 closure funcA() | |
// 第二次参数:$carry= closure funcA(); $item=closure funcB() 返回 closure funcB() | |
// 第三次参数:$carry= closure funcB(); $item=closure funcC() 返回 closure funcC() | |
// 此时的返回值为 closure funcC() 直接调用就是调用funcC | |
$finalFunc(); | |
} |
运行结果:
我们好像发现,离按顺序执行中间件已经越来越近了,那我们继续修改。
步骤三
改变如下
public function index() | |
{ | |
// 1. 为闭包添加参数, | |
$middelwareA = function ($request, \Closure $next) { | |
echo "middlewareA hanlde request <br/>"; | |
}; | |
$middlewareB = function ($request, \Closure $next) { | |
echo "middlewareB hanlde request <br/>"; | |
}; | |
$middlewareC = function ($request, \Closure $next) { | |
echo "middlewareC hanlde request <br/>"; | |
}; | |
$pipes = [$middelwareA, $middlewareB, $middlewareC]; | |
// 2. 增加一个变量request | |
$request = 'request'; | |
// 3. 增加一个闭包,模仿源码的中的参数$destination | |
$callback = function ($request) { | |
echo "callback handler request <br/>"; | |
}; | |
// 4. 改变第三个参数,传入一个闭包(下面添加了方法一个新的方法来返回闭包) | |
$finalFunc = array_reduce($pipes, $this->carry(),$this->prepareDestination($callback)); | |
// 运行解释: 此时最终返回的是一个闭包 closure funcC($request,$carry) | |
// 运行解释: 因为前面的callback funcA funcB 我们都没做任何处理的直接丢掉了 | |
$finalFunc($request); | |
} | |
// 6. 内容改简单一点,直接调用$item() | |
public function carry() | |
{ | |
return function ($carry, $item) { | |
return function ($request) use ($carry, $item) { | |
// 第一次参数:$carry= closure $callback($request); $item=closure funcA() 返回 closure($request,$carry) | |
// 第二次参数:$carry= closure funcA($request,$carry); $item=closure funcB() 返回 $item=closure funcB($request,$carry) | |
// 第二次参数:$carry= closure funcB($request,$carry); $item=closure funcC() 返回 $item=closure funcC($request,$carry) | |
return $item($request, $carry); | |
// $item($request, $carry); | |
}; | |
}; | |
} | |
public function prepareDestination($callback) | |
{ | |
return function ($request) use ($callback) { | |
return $callback($request); | |
}; | |
} |
运行结果 : middlewareC hanlde request
解释如代码注释所示
我们再微调一下代码
步骤四
public function index() | |
{ | |
// 1. 为闭包添加参数, | |
$middelwareA = function ($request, \Closure $next) { | |
echo "middlewareA hanlde request <br/>"; | |
// 此处的$next = closure $callback($request) | |
return $next($request); | |
}; | |
$middlewareB = function ($request, \Closure $next) { | |
echo "middlewareB hanlde request <br/>"; | |
// 此处的$next = closure funcA($request,closure $callback($request)) | |
return $next($request); | |
}; | |
$middlewareC = function ($request, \Closure $next) { | |
echo "middlewareC hanlde request <br/>"; | |
// 此处的$next = closure funcB($request,closure funcA(...)) | |
return $next($request); | |
}; | |
... ... // 中间代码省略 | |
$finalFunc($request); | |
} |
结果:
好的,中间件的原理实现的差不多了。
我们想想如何实现前后置中间件?
步骤五
public function index() | |
{ | |
$middelwareA = function ($request, \Closure $next) { | |
echo "before middlewareA hanlde request <br/>"; | |
// 此处的$next = closure $callback($request) | |
$response = $next($request); | |
echo "after middlewareA hanlde request <br/>"; | |
return $response; | |
}; | |
$middlewareB = function ($request, \Closure $next) { | |
echo "before middlewareB hanlde request <br/>"; | |
// 此处的$next = closure funcA($request,closure $callback($request)) | |
$response = $next($request); | |
echo "after middlewareB hanlde request <br/>"; | |
return $response; | |
}; | |
$middlewareC = function ($request, \Closure $next) { | |
echo "before middlewareC hanlde request <br/>"; | |
// 此处的$next = closure funcB($request,closure funcA(...)) | |
$response = $next($request); | |
echo "after middlewareC hanlde request <br/>"; | |
return $response; | |
}; | |
... ... // 中间代码省略 | |
$finalFunc($request); | |
} |
运行结果:
至此,大家应该了解了then是如何实现中间件的原理的了吧.(关于中间件的执行顺序就留给大家自己思考吧)