Laravel Middleware(中间件)执行流程源码分析
基于laravel10
分析
完整的执行流程我会放到后面的文章,这里只针对中间件的执行部分
先来说一下中间件的实现吧,在lavavel
中是通过Pipeline
(管道)+array_reduce
+array_reverse
内置函数来实现的
代码是这样子的
(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();
回到正文
在laravel框架中,中间件是分两步执行的,第一步是全局中间件,第二步是路由中间件
全局中间件
全局中间件是对应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\Router
类的gatherRouteMiddleware
方法做了什么处理
public function gatherRouteMiddleware(Route $route)
{
//这里是拿到定义的中间件和需要排除的中间件传入进去
return ②$this->resolveMiddleware($route->gatherMiddleware(), $route->excludedMiddleware());
}
②看一下Illuminate\Routing\Router
类的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;
}
排序去重完中间件之后就会传入到管道里面去执行后续的流程了
以上就是中间件执行大概流程了,如果有写的有误的地方,请大佬们指正
本作品采用《CC 协议》,转载必须注明作者和本文链接
推荐文章: