侯体宗的博客
  • 首页
  • Hyperf版
  • beego仿版
  • 人生(杂谈)
  • 技术
  • 关于我
  • 更多分类
    • 文件下载
    • 文字修仙
    • 中国象棋ai
    • 群聊
    • 九宫格抽奖
    • 拼图
    • 消消乐
    • 相册

基于laravel 10版本源码分析之Middleware(中间件)执行流程步骤

Laravel  /  管理员 发布于 1年前   402

在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;
}

排序去重完中间件之后就会传入到管道里面去执行后续的流程了。

如有不对欢迎评论指点!


  • 上一条:
    曝程序员自费帮公司搭建ChatGPT反被开除,如今ChatGPT无法使用,又被公司威胁上诉
    下一条:
    Laravel 10.20版本发布
  • 昵称:

    邮箱:

    0条评论 (评论内容有缓存机制,请悉知!)
    最新最热
    • 分类目录
    • 人生(杂谈)
    • 技术
    • linux
    • Java
    • php
    • 框架(架构)
    • 前端
    • ThinkPHP
    • 数据库
    • 微信(小程序)
    • Laravel
    • Redis
    • Docker
    • Go
    • swoole
    • Windows
    • Python
    • 苹果(mac/ios)
    • 相关文章
    • Laravel 11.15版本发布 - Eloquent Builder中添加的泛型(0个评论)
    • Laravel 11.14版本发布 - 新的字符串助手和ServeCommand改进(0个评论)
    • Laravel 11.12版本发布 - Artisan的`make`命令自动剪切`.php `扩展(0个评论)
    • Laravel的轻量型购物车扩展包:binafy/laravel-cart(0个评论)
    • Laravel 11.11版本发布 - 查看模型中的第三方关系:show(0个评论)
    • 近期文章
    • 在go中实现一个常用的先进先出的缓存淘汰算法示例代码(0个评论)
    • 在go+gin中使用"github.com/skip2/go-qrcode"实现url转二维码功能(0个评论)
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 欧盟关于强迫劳动的规定的官方举报渠道及官方举报网站(0个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf文件功能(0个评论)
    • Laravel从Accel获得5700万美元A轮融资(0个评论)
    • 在go + gin中gorm实现指定搜索/区间搜索分页列表功能接口实例(0个评论)
    • 在go语言中实现IP/CIDR的ip和netmask互转及IP段形式互转及ip是否存在IP/CIDR(0个评论)
    • 近期评论
    • 122 在

      学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..
    • 123 在

      Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..
    • 原梓番博客 在

      在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..
    • 博主 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..
    • 1111 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
    • 2016-10
    • 2016-11
    • 2017-07
    • 2017-08
    • 2020-03
    • 2020-04
    • 2020-05
    • 2020-06
    • 2020-07
    • 2020-08
    • 2020-09
    • 2020-10
    • 2020-11
    • 2021-01
    • 2021-02
    • 2021-03
    • 2021-04
    • 2021-05
    • 2021-06
    • 2021-07
    • 2021-08
    • 2021-09
    • 2021-10
    • 2021-11
    • 2021-12
    • 2022-01
    • 2022-02
    • 2022-03
    • 2022-04
    • 2022-05
    • 2022-06
    • 2022-07
    • 2022-08
    • 2022-09
    • 2022-10
    • 2022-11
    • 2022-12
    • 2023-01
    • 2023-02
    • 2023-03
    • 2023-04
    • 2023-05
    • 2023-06
    • 2023-07
    • 2023-08
    • 2023-09
    • 2023-10
    • 2023-11
    • 2023-12
    • 2024-01
    • 2024-02
    • 2024-03
    • 2024-04
    • 2024-05
    • 2024-06
    • 2024-07
    Top

    Copyright·© 2019 侯体宗版权所有· 粤ICP备20027696号 PHP交流群

    侯体宗的博客