深入理解并掌握ThinkPHP6中间件

silverwq
2022-10-08 / 0 评论 / 209 阅读 / 正在检测是否收录...

概述

thinkphp6中,中间件是针对http组件来说的,在http组件入库方法runWithRequest里

// 加载全局中间件
$this->loadMiddleware();
// 执行全局中间件
return $this->app->middleware->pipeline()// pipeline方法里将中间件转为闭包
    // 初始化
    ->send($request)
    // 执行
    ->then(function ($request) {
        // 最后执行路由分发
        return $this->dispatchToRoute($request);
    });

// 加载基础目录下的中间件
protected function loadMiddleware(): void
{
    if (is_file($this->app->getBasePath() . 'middleware.php')) {
        $this->app->middleware->import(include $this->app->getBasePath() . 'middleware.php');
    }
}

核心实现逻辑

通过中间件组件来实现,队列queue里有全局类型的中间件队列、route类型的队列、controller类型的队列

class Middleware
{
    // 队列
    protected $queue = [];

    /** *******添加中间件******** */
    public function import(array $middlewares = [], string $type = 'global'): void
    {
        foreach ($middlewares as $middleware) {
            $this->add($middleware, $type);
        }
    }
    public function add($middleware, string $type = 'global'): void
    {
        $middleware = $this->buildMiddleware($middleware, $type);
        if (!empty($middleware)) 
            // 队列里添加中间件
            $this->queue[$type][] = $middleware;
            // 队列去重
            $this->queue[$type]   = array_unique($this->queue[$type], SORT_REGULAR);
        }
    }
    // 中间件可以是数组、闭包、字符串
    protected function buildMiddleware($middleware, string $type): array
    {
        if (is_array($middleware)) {
            [$middleware, $params] = $middleware;
        }

        if ($middleware instanceof Closure) {
            // 闭包+参数
            return [$middleware, $params ?? []];
        }

        if (!is_string($middleware)) {
            throw new InvalidArgumentException('The middleware is invalid');
        }

        //中间件别名检查
        $alias = $this->app->config->get('middleware.alias', []);
        if (isset($alias[$middleware])) {
            $middleware = $alias[$middleware];
        }

        if (is_array($middleware)) {
            $this->import($middleware, $type);
            return [];
        }

        // 类名+handle方法+参数,如果没有传入方法名称,默认是handle方法
        return [[$middleware, 'handle'], $params ?? []];
    }
    public function route($middleware): void
    {
        // route类型的队列
        $this->add($middleware, 'route');
    }
    public function controller($middleware): void
    {
        // controller类型的队列
        $this->add($middleware, 'controller');
    }

    /** *******管道执行中间件******** */
    public function pipeline(string $type = 'global')
    {
        return (new Pipeline())
            // through是把中间转为闭包,方便后续执行
            ->through(array_map(function ($middleware) {
                // 返回中间件闭包数组,也就把中间件转为闭包
                return function ($request, $next) use ($middleware) {
                    // 调用中间件
                    [$call, $params] = $middleware;
                    if (is_array($call) && is_string($call[0])) {
                        $call = [$this->app->make($call[0]), $call[1]];
                    }
                    // 必须有request、next参数、外加自己传入的参数
                    // next参数是下一个要执行的中间件闭包,所以在回调函数里必须被调用,不然后续无法执行
                    $response = call_user_func($call, $request, $next, ...$params);

                    // 返回http响应类,因为最后一个执行的是dispatchToRoute
                    if (!$response instanceof Response) {
                        throw new LogicException('The middleware must return Response instance');
                    }
                    return $response;
                };

            }, $this->sortMiddleware($this->queue[$type] ?? [])))// 排序中间件,并且传给array_map,用于把中间件转为闭包
            // 设置如果有异常的时候,处理异常的方法名称
            ->whenException([$this, 'handleException']);
    }

    public function handleException($passable, Throwable $e)
    {
        // 系统异常类
        $handler = $this->app->make(Handle::class);
        $handler->report($e);
        return $handler->render($passable, $e);
    }

}

管道类

class Pipeline
{
   // 闭包形式的中间件存在这里
   protected $pipes = [];

    // 这种闭包管道
    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(),
            function ($passable) use ($destination) {
                try {
                    return $destination($passable);
                } catch (Throwable | Exception $e) {
                    return $this->handleException($passable, $e);
                }
            });

        return $pipeline($this->passable);
    }

    protected function carry()
    {
        return function ($stack, $pipe) {
            return function ($passable) use ($stack, $pipe) {
                try {
                    return $pipe($passable, $stack);
                } catch (Throwable | Exception $e) {
                    return $this->handleException($passable, $e);
                }
            };
        };
    }
}

管道的核心逻辑

简化下上述的管道逻辑,如下所示执行闭包:

$middleware = [
    function ($request1, $next) {
        echo 'request1闭包';
        // $next为request2闭包
        return $next($request1);
    },
    function ($request2, $next) {
        echo 'request2闭包';
        // $next为request3闭包
        return $next($request2);
    },
    function ($request3, $next) {
        echo 'request3闭包';
        // $next为last闭包
        return $next($request3);
    }
];
// 需要倒叙,因为闭包执行是按相反的顺序进行的
$middleware = array_reverse($middleware);

$pipe = array_reduce($middleware, function ($stack, $pipe) {
    // $stack永远指向上一个闭包
    return function ($passable) use ($stack, $pipe) {
        // 第一次执行:$stack=last闭包
        // 第二次执行:$stack=request1闭包
        // 第三次执行:$stack=request2闭包,此时$pipe为request3闭包
        return $pipe($passable, $stack);
    };
}, function ($requestLast) {
    echo 'last闭包';
});
$pipe('request');
0

评论 (0)

取消