基于laravel 10版本源码分析之Middleware(中间件)执行流程步骤
Laravel  /  管理员 发布于 1年前   385
在lavavel中是通过Pipeline(管道) + array_reduce + array_reverse内置函数来实现中间件功能的。
还有在laravel框架中,中间件是分两步执行的,第一步是全局中间件,第二步是路由中间件。
实现中间件源码:
(new Pipeline($this->container))
->send($request)
->through($middleware)
->then(fn($request) => xxx);
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)
{
$pipeline = array_reduce(
array_reverse($this->pipes()), $this->carry(), $this->prepareDestination($destination)
);
return $pipeline($this->passable);
}
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);
}
};
};
}
以上代码执行流程:
主要是来看一下then 方法,先是把中间件数组通过array_reverse顺序翻转 (目的是为了配合array_reduce),
因为 array_reduce 是迭代数组元素传入回调函数里面,第一次迭代就用第三个参数作为初始值,
看carry方法就能知道,每一次迭代就是闭包套闭包,最开始的就在最里面,
所以需要翻转数组,上面array_reduce执行完后得到的差不多是下面的效果,
只是里面闭包是 use 进去的,我这里举例是固定的。
$f = function () {
$next2 = function () {
$next1 = function () {
};
//前置
$next1();
//后置
};
//前置
$next2();
//后置
};
$f();
全局中间件
全局中间件是对应 App\Http\Kernel 类的 $middleware 属性
全局中间件是在
Illuminate\Foundation\Http\Kernel类的sendRequestThroughRouter方法中执行的,
这里也没有做什么特别的处理,就是拿 $middleware 属性数组传入进去了
protected function sendRequestThroughRouter($request)
{
...
return (new Pipeline($this->app))
->send($request)
->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
->then($this->dispatchToRouter());
}
路由中间件
主要是看这里,路由中间件做了很多处理,让我们来看一下做了哪些处理
先看 Illuminate\Foundation\Http\Kernel 类的 syncMiddlewareToRouter方法
protected function syncMiddlewareToRouter()
{
//这里就是把中间件优先级排序列表赋值到路由单例里面
$this->router->middlewarePriority = $this->middlewarePriority;
//这里是把中间件组赋值到路由单例里面
foreach ($this->middlewareGroups as $key => $middleware) {
$this->router->middlewareGroup($key, $middleware);
}
//这里是把中间件别名赋值到路由单例里面
//$routeMiddleware这个属性好像在10版本里面没有直接用到,不知道是不是以前的版本是用这个属性来定义别名的
foreach (array_merge($this->routeMiddleware, $this->middlewareAliases) as $key => $middleware) {
$this->router->aliasMiddleware($key, $middleware);
}
}
再直接看 Illuminate\Routing\Router 类的 runRouteWithinStack 方法
protected function runRouteWithinStack(Route $route, Request $request)
{
//这里是判断是否禁用中间件,这里不清楚为啥不像全局中间件那样调用 $this->app->shouldSkipMiddleware()
$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(fn ($request) => $this->prepareResponse(
$request, $route->run()
));
}
看Illuminate\Routing\Route 类的 gatherRouteMiddleware、resolveMiddleware方法做了什么处理
gatherRouteMiddleware
public function gatherRouteMiddleware(Route $route)
{
//这里是拿到定义的中间件和需要排除的中间件传入进去
return ②$this->resolveMiddleware($route->gatherMiddleware(), $route->excludedMiddleware());
}
resolveMiddleware
public function resolveMiddleware(array $middleware, array $excluded = [])
{
//排除中间件
$excluded = collect($excluded)->map(function ($name) {
//这里是去解析中间件
return (array) ③MiddlewareNameResolver::resolve($name, $this->middleware, $this->middlewareGroups);
})->flatten()->values()->all();
//当前路由需要执行的中间件(去掉排除的中间件)
$middleware = collect($middleware)->map(function ($name) {
return (array) MiddlewareNameResolver::resolve($name, $this->middleware, $this->middlewareGroups);
})->flatten()->reject(function ($name) use ($excluded) {
if (empty($excluded)) {
return false;
}
if ($name instanceof Closure) {
return false;
}
if (in_array($name, $excluded, true)) {
return true;
}
if (! class_exists($name)) {
return false;
}
$reflection = new ReflectionClass($name);
//这里是判断是否是排除中间件中某一个中间件的子类
return collect($excluded)->contains(
fn ($exclude) => class_exists($exclude) && $reflection->isSubclassOf($exclude)
);
})->values();
//排序
return ④$this->sortMiddleware($middleware);
}
在看一下 Illuminate\Routing\MiddlewareNameResolver 类的 resolve 方法做了什么处理
public static function resolve($name, $map, $middlewareGroups)
{
//判断是否是闭包
if ($name instanceof Closure) {
return $name;
}
//判断是否别名 且别名对应的也是闭包
if (isset($map[$name]) && $map[$name] instanceof Closure) {
return $map[$name];
}
//判断是否是中间件组
if (isset($middlewareGroups[$name])) {
//解析中间件组
return static::parseMiddlewareGroup($name, $map, $middlewareGroups);
}
//分离名称和参数
[$name, $parameters] = array_pad(explode(':', $name, 2), 2, null);
//把别名替换类名拼接参数
return ($map[$name] ?? $name).(! is_null($parameters) ? ':'.$parameters : '');
}
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;
}
//拿到中间件名和参数
[$middleware, $parameters] = array_pad(
explode(':', $middleware, 2), 2, null
);
//判断是否是别名
if (isset($map[$middleware])) {
$middleware = $map[$middleware];
}
$results[] = $middleware.($parameters ? ':'.$parameters : '');
}
return $results;
}
上面就是把中间件组和别名处理找到对应的类名,后续就可以直接去实例化调用
看Illuminate\Routing\Router类的sortMiddleware方法,
就是拿$middlewarePriority属性数组中间的顺序去排序当前请求路由上的中间件
protected function sortMiddleware(Collection $middlewares)
{
return (new SortedMiddleware($this->middlewarePriority, $middlewares))->all();
}
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;
}
//看是否能拿到优先级数组的下标
$priorityIndex = ⑤$this->priorityMapIndex($priorityMap, $middleware);
//表示能拿到下标
if (! is_null($priorityIndex)) {
//如果存在上一次的下标 且 这次的下标小于上一次的下标 则需要进行排序,把这次的放到上次下标的前面,然后删除这次下标+1的元素(就是未排序前的自己)
if (isset($lastPriorityIndex) && $priorityIndex < $lastPriorityIndex) {
//这里需要重新进行新的排序 递归处理
return $this->sortMiddleware(
$priorityMap, array_values($this->moveMiddleware($middlewares, $index, $lastIndex))
);
}
$lastIndex = $index;
$lastPriorityIndex = $priorityIndex;
}
}
//去重
return ⑥Router::uniqueMiddleware($middlewares);
}
}
看Illuminate\Routing\SortedMiddleware类的priorityMapIndex 方法做了什么处理
protected function priorityMapIndex($priorityMap, $middleware)
{
foreach ($this->middlewareNames($middleware) as $name) {
$priorityIndex = array_search($name, $priorityMap);
if ($priorityIndex !== false) {
return $priorityIndex;
}
}
}
//这里返回一个迭代
protected function middlewareNames($middleware)
{
//拿到中间件类名
$stripped = head(explode(':', $middleware));
yield $stripped;
//拿到所有接口包括父类的接口
$interfaces = @class_implements($stripped);
if ($interfaces !== false) {
foreach ($interfaces as $interface) {
yield $interface;
}
}
//拿到所有继承的父类
$parents = @class_parents($stripped);
if ($parents !== false) {
foreach ($parents as $parent) {
yield $parent;
}
}
}
最后看Illuminate\Routing\Router类的uniqueMiddleware方法做了什么处理
public static function uniqueMiddleware(array $middleware)
{
$seen = [];
$result = [];
foreach ($middleware as $value) {
//这里如果是对象(闭包) 则拿到对象的唯一标识符
$key = \is_object($value) ? \spl_object_id($value) : $value;
if (! isset($seen[$key])) {
$seen[$key] = true;
$result[] = $value;
}
}
return $result;
}
排序去重完中间件之后就会传入到管道里面去执行后续的流程了。
如有不对欢迎评论指点!
122 在
学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..123 在
Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..原梓番博客 在
在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..博主 在
佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..1111 在
佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
Copyright·© 2019 侯体宗版权所有·
粤ICP备20027696号