内容简介:Laravel HTTP——路由中间件的别名解析与排序源码解析
前言
本文 GitBook 地址: https://www.gitbook.com/book/leoyang90/laravel-source-analysis
当进行了路由匹配与路由参数绑定后,接下来就要进行路由闭包或者控制器的运行,在此之前,本文先介绍中间件的相关源码。
中间件的搜集
由于定义的中间件方式很灵活,所以在运行控制器或者路由闭包之前,我们需要先将在各个地方注册的所有中间件都搜集到一起,然后集中排序。
public function dispatchToRoute(Request $request)
{
$route = $this->findRoute($request);
$request->setRouteResolver(function () use ($route) {
return $route;
});
$this->events->dispatch(new Events\RouteMatched($route, $request));
$response = $this->runRouteWithinStack($route, $request);
return $this->prepareResponse($request, $response);
}
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()
);
});
}
public function gatherRouteMiddleware(Route $route)
{
$middleware = collect($route->gatherMiddleware())->map(function ($name) {
return (array) MiddlewareNameResolver::resolve($name, $this->middleware, $this->middlewareGroups);
})->flatten();
return $this->sortMiddleware($middleware);
}
路由的中间件大致有两个大的来源:
- 在路由的定义过程中,利用关键字
middleware
为路由添加中间件,这种中间件都是在文件App\Http\Kernel
中$middlewareGroups
、$routeMiddleware
这两个数组定义的中间件别名。 - 在路由控制器的构造函数中,添加中间件,可以在这里定义一个闭包作为中间件,也可以利用中间件别名。
public function gatherMiddleware()
{
if (! is_null($this->computedMiddleware)) {
return $this->computedMiddleware;
}
$this->computedMiddleware = [];
return $this->computedMiddleware = array_unique(array_merge(
$this->middleware(), $this->controllerMiddleware()
), SORT_REGULAR);
}
路由定义的中间件
public function middleware($middleware = null)
{
if (is_null($middleware)) {
return (array) Arr::get($this->action, 'middleware', []);
}
if (is_string($middleware)) {
$middleware = func_get_args();
}
$this->action['middleware'] = array_merge(
(array) Arr::get($this->action, 'middleware', []), $middleware
);
return $this;
}
控制器定义的中间件
public function controllerMiddleware()
{
if (! $this->isControllerAction()) {
return [];
}
return ControllerDispatcher::getMiddleware(
$this->getController(), $this->getControllerMethod()
);
}
public function getController()
{
$class = $this->parseControllerCallback()[0];
if (! $this->controller) {
$this->controller = $this->container->make($class);
}
return $this->controller;
}
protected function getControllerMethod()
{
return $this->parseControllerCallback()[1];
}
protected function parseControllerCallback()
{
return Str::parseCallback($this->action['uses']);
}
public static function parseCallback($callback, $default = null)
{
return static::contains($callback, '@') ? explode('@', $callback, 2) : [$callback, $default];
}
当前的路由如果使用控制器的时候,就要解析属性 use
,解析出控制器的类名与类方法。接下来就需要 ControllerDispatcher
类。
在讲解 ControllerDispatcher
类之前,我们需要先了解一下控制器中间件:
abstract class Controller
{
public function middleware($middleware, array $options = [])
{
foreach ((array) $middleware as $m) {
$this->middleware[] = [
'middleware' => $m,
'options' => &$options,
];
}
return new ControllerMiddlewareOptions($options);
}
}
class ControllerMiddlewareOptions
{
protected $options;
public function __construct(array &$options)
{
$this->options = &$options;
}
public function only($methods)
{
$this->options['only'] = is_array($methods) ? $methods : func_get_args();
return $this;
}
public function except($methods)
{
$this->options['except'] = is_array($methods) ? $methods : func_get_args();
return $this;
}
}
在为控制器定义中间的是,可以为中间件利用 only
指定在当前控制器中调用该中间件的特定控制器方法,也可以利用 except
指定在当前控制器禁止调用中间件的方法。这些信息都保存在控制器的变量 middleware
的 options
中。
在搜集控制器的中间件时,就要利用中间件的这些信息:
class ControllerDispatcher
{
public static function getMiddleware($controller, $method)
{
if (! method_exists($controller, 'getMiddleware')) {
return [];
}
return collect($controller->getMiddleware())->reject(function ($data) use ($method) {
return static::methodExcludedByOptions($method, $data['options']);
})->pluck('middleware')->all();
}
protected static function methodExcludedByOptions($method, array $options)
{
return (isset($options['only']) && ! in_array($method, (array) $options['only'])) ||
(! empty($options['except']) && in_array($method, (array) $options['except']));
}
}
在 ControllerDispatcher
类中,利用了 reject
函数对每一个中间件都进行了控制器方法的判断,排除了不支持该控制器方法的中间件。pluck
函数获取了控制器 $this->middleware[]
数组中 middleware
的所有元素。
中间件的解析
中间件解析主要的工作是将路由中中间件的别名转化为中间件全程,主要流程为:
class MiddlewareNameResolver
{
public static function resolve($name, $map, $middlewareGroups)
{
if ($name instanceof Closure) {
return $name;
} elseif (isset($map[$name]) && $map[$name] instanceof Closure) {
return $map[$name];
} elseif (isset($middlewareGroups[$name])) {
return static::parseMiddlewareGroup(
$name, $map, $middlewareGroups
);
} else {
list($name, $parameters) = array_pad(explode(':', $name, 2), 2, null);
return (isset($map[$name]) ? $map[$name] : $name).
(! is_null($parameters) ? ':'.$parameters : '');
}
}
}
可以看出,解析的中间件对象有三种:闭包、中间件别名、中间件组。
- 对于闭包来说,
resolve
直接返回闭包; - 对于中间件别名来说,例如
auth
,会从App\Http\Kernel
文件$routeMiddleware
数组中寻找中间件全名\Illuminate\Auth\Middleware\Authenticate::class
- 对于具有参数的中间件别名来说,例如
throttle:60,1
,会将别名转化为全名\Illuminate\Routing\Middleware\ThrottleRequests::60,1
- 对于中间件组来说,会调用
parseMiddlewareGroup
函数。
protected static function parseMiddlewareGroup($name, $map, $middlewareGroups)
{
$results = [];
foreach ($middlewareGroups[$name] as $middleware) {
if (isset($middlewareGroups[$middleware])) {
$results = array_merge($results, static::parseMiddlewareGroup(
$middleware, $map, $middlewareGroups
));
continue;
}
list($middleware, $parameters) = array_pad(
explode(':', $middleware, 2), 2, null
);
if (isset($map[$middleware])) {
$middleware = $map[$middleware];
}
$results[] = $middleware.($parameters ? ':'.$parameters : '');
}
return $results;
}
可以看出,对于中间件组来说,就要从 App\Http\Kernel
文件 $$middlewareGroups
数组中寻找组内的多个中间件,例如中间件组 api
:
'api' => [
'throttle:60,1',
'bindings',
]
解析出的中间件可能存在参数,别名转化为全名后函数返回。值得注意的是,中间件组内不一定都是别名,也有可能是中间件组的组名,例如:
'api' => [
'throttle:60,1',
'web',
]
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
],
这时,就需要迭代解析。
中间件的排序
public function gatherRouteMiddleware(Route $route)
{
$middleware = collect($route->gatherMiddleware())->map(function ($name) {
return (array) MiddlewareNameResolver::resolve($name, $this->middleware, $this->middlewareGroups);
})->flatten();
return $this->sortMiddleware($middleware);
}
将所有中间件搜集并解析完毕后,接下来就要对中间件的调用顺序做一些调整,以确保中间件功能正常。
protected $middlewarePriority = [
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\Illuminate\Auth\Middleware\Authenticate::class,
\Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\Illuminate\Auth\Middleware\Authorize::class,
];
数组 middlewarePriority
中保存着必须有一定顺序的中间件,例如 StartSession
中间件就必须运行在 ShareErrorsFromSession
之前。因此一旦路由中有这两个中间件,那么就要确保两者的顺序一致。
中间件的 排序 由函数 sortMiddleware
负责:
class SortedMiddleware extends Collection
{
public function __construct(array $priorityMap, $middlewares)
{
if ($middlewares instanceof Collection) {
$middlewares = $middlewares->all();
}
$this->items = $this->sortMiddleware($priorityMap, $middlewares);
}
protected function sortMiddleware($priorityMap, $middlewares)
{
$lastIndex = 0;
foreach ($middlewares as $index => $middleware) {
if (! is_string($middleware)) {
continue;
}
$stripped = head(explode(':', $middleware));
if (in_array($stripped, $priorityMap)) {
$priorityIndex = array_search($stripped, $priorityMap);
if (isset($lastPriorityIndex) && $priorityIndex < $lastPriorityIndex) {
return $this->sortMiddleware(
$priorityMap, array_values(
$this->moveMiddleware($middlewares, $index, $lastIndex)
)
);
} else {
$lastIndex = $index;
$lastPriorityIndex = $priorityIndex;
}
}
}
return array_values(array_unique($middlewares, SORT_REGULAR));
}
protected function moveMiddleware($middlewares, $from, $to)
{
array_splice($middlewares, $to, 0, $middlewares[$from]);
unset($middlewares[$from + 1]);
return $middlewares;
}
}
函数的方法很简单,检测当前中间件数组,查看是否存在中间件是数组 middlewarePriority
内元素。如果发现了两个中间件不符合顺序,那么就要调换中间件顺序,然后进行迭代。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- Koa 系列 —— Koa 中间件机制解析
- 消息中间件 RocketMQ 源码解析 —— 调试环境搭建
- redux, koa, express 中间件实现对比解析
- Koa2 中间件原理解析 —— 看了就会写
- 中间件(WAS、WMQ)运维 9个常见难点解析
- Laravel HTTP——SubstituteBindings 中间件的使用与源码解析
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Impractical Python Projects
Lee Vaughan / No Starch Press / 2018-11 / USD 29.95
Impractical Python Projects picks up where the complete beginner books leave off, expanding on existing concepts and introducing new tools that you’ll use every day. And to keep things interesting, ea......一起来看看 《Impractical Python Projects》 这本书的介绍吧!