欢迎指正内容不严谨或有误的地方!
Reference
管道与中间件
// src/Illuminate/Foundation/Http/Kernel.php
//
protected function sendRequestThroughRouter($request)
{
$this->app->instance('request', $request);
Facade::clearResolvedInstance('request');
// 这一部分我们在前面的服务提供者部分讲解了
$this->bootstrap();
// 管道
// 这里才是通过管道模式利用中间件对请求的处理
return (new Pipeline($this->app))
->send($request) // 管道运送的资源(用户请求)
->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware) // 管道介质过滤器(中间件)
->then($this->dispatchToRouter()); // 核心代码
}
在处理用户请求的管道中,(new Pipeline($this->app))
仅仅是是传递了服务容器进入管道,->send($request)
将用户请求赋给了 Pipeline
实例的 $passable
属性,->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
则是将相关中间件注入到了 Pipeline
的 pipes
属性中,所以说最终的核心逻辑还是在 ->then($this->dispatchToRouter());
这段代码中。
public function then(Closure $destination)
{
// array_reduce 用回调函数迭代地将数组简化为单一的值
// dd($this->pipes());
/*
$this->pipes() = [
\App\Http\Middleware\TrustProxies::class,
\Fruitcake\Cors\HandleCors::class,
\App\Http\Middleware\PreventRequestsDuringMaintenance::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
];
*/
// 第一个参数为要遍历的数组
// 第二个参数为对遍历元素进行操作的回调函数,回调函数的第一个参数为上次迭代后得到的值,第二个参数为此次迭代的值
// 第三个参数默认为 NULL,其代表了第一次迭代时回调函数的初始值
$pipeline = array_reduce(
array_reverse($this->pipes()), $this->carry(), $this->prepareDestination($destination)
);
// 这里才是最后调用第三参数返回的匿名函数
return $pipeline($this->passable);
}
可以看到这里最为关键的就是 array_reduce()
函数这段代码了。
**array_reduce** ( array $array , callable $callback [, mixed $initial = **NULL** ] ) : mixed
用回调函数迭代地将数组简化为单一的值,其第一个参数为要进行遍历迭代的数组,第二个参数为对其数组元素进行操作的回调函数callback ( mixed $carry , mixed $item ) : mixed
;需要注意的是array_reduce
函数是可以有第三个参数的,该值默认为NULL
。此外就是回调函数中的第一个参数即为上次回调函数迭代后的值(第一次默认是array_reduece
的第三参数),第二个参数则为此次迭代的元素。
这里知道了 array_reduce
函数的作用后我们就可以尝试去理解这段代码了。先需要知道的是 $this->pipes()
即为我们之前注入的中间件们,也就是 Http/Kernel.php
中的 $middleware
属性。而 $this->carray()
也就是我们将使用的回调函数。
protected function carry()
{
// $stack 就是 array_reduce 的第三个参数,当前为 匿名函数
// $pile 为当前迭代的中间件
// $passable 为 request 对象
// 作为 array_reduce 每次迭代的返回值,只执行了外面的一层匿名函数,也就是每次迭代过程中返回的都是一个闭包
// 比如中间件是 【A B C D】 carry init
// 这里的 $stack 只有第一次是我们初始值,而作为初始值其被包裹在闭包的最外层
// 迭代结果
// init(D(C(B(A()))))
// 也就是每次迭代都会将后面的闭包放在前面的闭包里面进行嵌套最终嵌套出一个中间件逻辑层层嵌套的闭包
// 而业务逻辑被嵌套在最外层
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 所有的中间件返回的都是 $next($request) 也就是 $stack($passable)
$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
变量分别对应了回调函数的上次迭代值和当前迭代元素(中间件),$passable
顾名思义就是前面讲到的 $request
用户请求。仅接着在匿名函数中就对 $pipe
的类型进行了判断,很显然我们数组中的中间件都以其命名空间的字符串形式存在,因此会走向第二个判断分支,很显然这里对中间件进行实例化,并处理好了中间件在后续过程中需要调用的参数 $parameters
,后续的 $carry
即为中间件调用后得到的结果(查看结果可以看到: 这里的 $carry
所有的中间件返回的都是 $next($request)
也就是 $stack($passable)
),很显然返回的是一个匿名函数。
到这里就已经很清楚了, $this->carry()
返回的是对中间件逻辑封装后的闭包,而 array_reduce
会将这个闭包传递给下一次迭代,这个 $stack
的初始值是 $this->prepareDestination($destination)
也是一个闭包,而这个每个闭包的返回结果都是上一个闭包对请求的处理 $next($request)
,也就是其他地方提到的洋葱模型,将每一层中间件逻辑一层层的包裹在了其中,只有里层的中间件处理完毕才会轮到外层的中间件进行处理,这也是为什么要进行 array_reverse
的原因,就是为了将排在前面的中间件放在最里面。
处理用户请求
看到这里我们应该也明白了最初的初始闭包是什么了,很显然其就是我们在走完所有中间件后的业务逻辑所在。
protected function prepareDestination(Closure $destination)
{
return function ($passable) use ($destination) {
try {
return $destination($passable);
} catch (Throwable $e) {
return $this->handleException($passable, $e);
}
};
}
可以看到其最后调用的是 $destination($passable);
,而 $destination
也就是上面的 $this->dispatchToRouter()
:
protected function dispatchToRouter()
{
return function ($request) {
$this->app->instance('request', $request);
return $this->router->dispatch($request);
};
}
最终是走到了:
// src/Illuminate/Routing/Router.php
protected function runRouteWithinStack(Route $route, Request $request)
{
$shouldSkipMiddleware = $this->container->bound('middleware.disable') &&
$this->container->make('middleware.disable') === true;
$middleware = $shouldSkipMiddleware ? [] : $this->gatherRouteMiddleware($route);
return (new Pipeline($this->container))
->send($request)
->through($middleware)
->then(function ($request) use ($route) {
return $this->prepareResponse(
$request, $route->run()
);
});
}
可能有人会奇怪为什么这里还有管道和中间件的处理,打印 $middleware
变量后可以发现这里是对 web
中间件组的处理。经过我们对管道的了解,我们很清楚这里的核心逻辑就是 $route->run()
了:
public function run()
{
$this->container = $this->container ?: new Container;
try {
if ($this->isControllerAction()) {
return $this->runController();
}
return $this->runCallable();
} catch (HttpResponseException $e) {
return $e->getResponse();
}
}
很显然这里应该是区分了控制器行为调用和直接调用(也就是在路由中使用闭包的形式进行调用):
// src/Illuminate/Routing/Route.php
protected function runController()
{
return $this->controllerDispatcher()->dispatch(
$this, $this->getController(), $this->getControllerMethod()
);
}
// src/Illuminate/Routing/ControllerDispatcher.php
public function dispatch(Route $route, $controller, $method)
{
$parameters = $this->resolveClassMethodDependencies(
$route->parametersWithoutNulls(), $controller, $method
);
if (method_exists($controller, 'callAction')) {
return $controller->callAction($method, $parameters);
}
return $controller->{$method}(...array_values($parameters));
}
可以看到最后是对控制器中的方法进行了调用。