前置知识
中间件作用
在执行具体controller之前做一些预处理,比如权限判断,参数校验等等
array_reduce()
array_reduce — Iteratively reduce the array to a single value using a callback function
这里要掌握array_reduce()方法的原理, 文档的第二个node写的很清楚.原理如下:
<?php
function array_reduce($array, $callback, $initial=null)
{
$acc = $initial;
foreach($array as $a)
$acc = $callback($acc, $a);
return $acc;
}
?>
中间件实现
本质是通过栈的概念来实现的,逆序将注册的中间件压入栈,弹出时注入(传参等方式)全局request,最后弹出执行业务逻辑,进入response阶段.
实现方法1 遍历数组
gin框架的中间件实现,可以看到就是一个循环
github.com/gin-gonic/gin@v1.6.3/context.go 158
func (c *Context) Next() {
c.index++
for c.index < int8(len(c.handlers)) {
c.handlers[c.index](c)
c.index++
}
}
练习:
<?php
$context = [
'p' => 0,
'r' => 'hello',
];
$a = function ($context) {
echo "a start" . PHP_EOL;
$context['p']++;
echo "a end" . PHP_EOL;
};
$b = function ($context) {
echo "b start" . PHP_EOL;
$context['p']++;
echo "b end" . PHP_EOL;
};
$stack = [$b, $a];
while ($r = array_pop($stack)) {
$r($context);
}
实现方法2 闭包
laravel就是通过闭包实现的.
练习:
<?php
//栈式调用
$carry = function ($stack, $pipe) {
echo 'carry ' . $pipe . PHP_EOL;
var_dump($stack);
return function (...$arg) use ($stack, $pipe) {
echo "carry closure start " . $pipe . PHP_EOL;
// echo "carry closure run arg = " . var_export($arg, true) . PHP_EOL;
$stack($arg);
echo "carry closure end " . $pipe . PHP_EOL;
};
};
$default = function ($a = 'default') {
echo "default func" . PHP_EOL;
};
$pips = [
'apple',
'banana',
'candy',
'doctor',
];
$first = array_reduce(array_reverse($pips), $carry, $default);
$first(1, 2, 3);
找到一个grpc的interceptor的递归实现讲解文章,也是异曲同工 gRPC拦截器那点事,希望帮到你
laravel源码
vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php
/**
* Send the given request through the middleware / router.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
protected function sendRequestThroughRouter($request)
{
$this->app->instance('request', $request);
Facade::clearResolvedInstance('request');
$this->bootstrap();
/** @var $pipline \Illuminate\Routing\Pipeline */
$pipline = new Pipeline($this->app);
return $pipline
->send($request)
->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
->then($this->dispatchToRouter());
}
$this->dispatchToRouter()获得路由逻辑和执行方法的闭包,在then方法里作为栈底调用.中间件逻辑在then方法里
/**
* Run the pipeline with a final destination callback.
*
* @param \Closure $destination
* @return mixed
*/
public function then(Closure $destination)
{
$pipeline = array_reduce(
array_reverse($this->pipes()), $this->carry(), $this->prepareDestination($destination)
);
return $pipeline($this->passable);
}
pipes是中间件类名数组,carry方法返回闭包,最后一个是路由实现的闭包
这里的关键就是array_reduce的作用,最后调用由闭包组成的栈.
/**
* Get a Closure that represents a slice of the application onion.
*
* @return \Closure
*/
protected function carry()
{
return function ($stack, $pipe) {
return function ($passable) use ($stack, $pipe) {
try {
if (is_callable($pipe)) {
// If the pipe is a callable, then we will call it directly, but otherwise we
// will resolve the pipes out of the dependency container and call it with
// the appropriate method and arguments, returning the results back out.
return $pipe($passable, $stack);
} elseif (! is_object($pipe)) {
[$name, $parameters] = $this->parsePipeString($pipe);
// If the pipe is a string we will parse the string and resolve the class out
// of the dependency injection container. We can then build a callable and
// execute the pipe function giving in the parameters that are required.
$pipe = $this->getContainer()->make($name);
$parameters = array_merge([$passable, $stack], $parameters);
} else {
// If the pipe is already an object we'll just make a callable and pass it to
// the pipe as-is. There is no need to do any extra parsing and formatting
// since the object we're given was already a fully instantiated object.
$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);
}
};
};
}
闭包中的$stack就是闭包实现的调用栈,$pipe会在回调方法中运行.至此分析结束.