ThinkPHP6 源码阅读(八):控制器操作的执行

说明

更新日志:2019-10-31 此篇原先是衔接上一篇中间件来分析的,由于TP6.0正式版完全改变了中间件的实现方法,于是重新写了一篇中间件的分析,见:博客:ThinkPHP 6.0 管道模式与中间件的实现分析 。后面的分析改为衔接这一篇。

再次从runWithRequest方法开始:

protected function runWithRequest(Request $request)
{
    .
    .
    .

    return $this->app->middleware->pipeline()
        ->send($request)
        ->then(function ($request) {
            return $this->dispatchToRoute($request);
        });
}

控制器操作的代码实际上都包含在$this->dispatchToRoute($request)中,展开来看:

protected function dispatchToRoute($request)
{
    $withRoute = $this->app->config->get('app.with_route', true) ? function () {
        $this->loadRoutes();
    } : null;

    return $this->app->route->dispatch($request, $withRoute);
}

先抛开路由不管,主角是dispatch方法:

public function dispatch(Request $request, $withRoute = null)
{
    $this->request = $request;
    $this->host    = $this->request->host(true);
    $this->init();

    if ($withRoute) {
        //加载路由
        $withRoute();
        $dispatch = $this->check();
    } else {
        $dispatch = $this->url($this->path());
    }

    $dispatch->init($this->app);

    return $this->app->middleware->pipeline('route')
        ->send($request)
        ->then(function () use ($dispatch) {
            return $dispatch->run();
        });
}

重点关注后面的return语句,这里又给控制器操作的代码包裹上了中间件闭包,其原理也跟前面分析的全局中间件原理一样,不再赘述。重点的重点关注run方法:

public function run(): Response
{
    // HTTP的OPTIONS方法用于获取目的资源所支持的通信选项。
    if ($this->rule instanceof RuleItem && $this->request->method() == 'OPTIONS' && $this->rule->isAutoOptions()) {
        $rules = $this->rule->getRouter()->getRule($this->rule->getRule());
        $allow = [];
        foreach ($rules as $item) {
            $allow[] = strtoupper($item->getMethod());
        }

        return Response::create('', '', 204)->header(['Allow' => implode(', ', $allow)]);
    }

    //这里的exec方法位于Controller类
    $data = $this->exec();
    // 控制器操作返回的数据,进一步加工,设置Http报头、状态码等,返回一个Response对象
    return $this->autoResponse($data);
}

分析详见注释。主要的逻辑在$data = $this->exec(),其中,注意exec方法位于think\route\dispatch\Controller类,其代码如下:

public function exec()
{
    try {
        // 实例化控制器
        $instance = $this->controller($this->controller);
    } catch (ClassNotFoundException $e) {
        throw new HttpException(404, 'controller not exists:' . $e->getClass());
    }

    //A 注册控制器中间件
    $this->registerControllerMiddleware($instance);
    // 这里跟前面全局中间件原理一样,不再分析
    return $this->app->middleware->pipeline('controller')
        ->send($this->request)
        ->then(function () use ($instance) {
            // 获取当前操作名
            $action = $this->actionName . $this->rule->config('action_suffix');

            if (is_callable([$instance, $action])) {
                $vars = $this->request->param();
                try {
                    $reflect = new ReflectionMethod($instance, $action);
                    // 严格获取当前操作方法名
                    $actionName = $reflect->getName();
                    $this->request->setAction($actionName);
                } catch (ReflectionException $e) {
                    $reflect = new ReflectionMethod($instance, '__call');
                    $vars    = [$action, $vars];
                    $this->request->setAction($action);
                }
            } else {
                // 操作不存在
                throw new HttpException(404, 'method not exists:' . get_class($instance) . '->' . $action . '()');
            }
            // 控制器操作的执行在这里
            $data = $this->app->invokeReflectMethod($instance, $reflect, $vars);

            return $this->autoResponse($data);
        });
}

详细分析见代码注释,以下再展开分析控制器的实例化和控制器中间件的注册。

A 控制器的实例化

执行实例化的think\route\dispatch\Controller类的controller方法代码如下:

public function controller(string $name)
{
    // 是否使用控制器后缀
    $suffix = $this->rule->config('controller_suffix') ? 'Controller' : '';
    // 访问控制器层名称
    $controllerLayer = $this->rule->config('controller_layer') ?: 'controller';
    // 空控制器名称
    $emptyController = $this->rule->config('empty_controller') ?: 'Error';
    //获取控制器完整的类名
    $class = $this->app->parseClass($controllerLayer, $name . $suffix);
    // 如果这个类存在
    if (class_exists($class)) {
        //通过容器获取实例(非单例模式)
        return $this->app->make($class, [], true);
        //不存在时,如果有空控制器的类存在
    } elseif ($emptyController && class_exists($emptyClass = $this->app->parseClass($controllerLayer, $emptyController . $suffix))) {
        //同理,实例化空控制器
        return $this->app->make($emptyClass, [], true);
    }
    // 如果找不到控制器的类,且连控控制器也没有,抛出错误
    throw new ClassNotFoundException('class not exists:' . $class, $class);
}

具体分析见注释。

B 控制器中间件注册

执行注册的registerControllerMiddleware方法代码如下:

protected function registerControllerMiddleware($controller): void
{
    // 获取反射类对象
    $class = new ReflectionClass($controller);
    // 检查控制器类是否有middleware属性
    if ($class->hasProperty('middleware')) {
        //提取middleware变量
        $reflectionProperty = $class->getProperty('middleware');
        //设置可见性为公有
        $reflectionProperty->setAccessible(true);
        //获取middleware属性的值
        $middlewares = $reflectionProperty->getValue($controller);
        //解析控制器中间件配置
        foreach ($middlewares as $key => $val) {
            if (!is_int($key)) {
                //如果有设置only属性
                //$this->request->action(true)获取当前操作名并转为小写
                //$val['only']各元素也转为小写,然后判断当前操作是否在$val['only']里面
                //不在则跳过(说明该操作不需要执行该中间件)
                if (isset($val['only']) && !in_array($this->request->action(true), array_map(function ($item) {
                    return strtolower($item);
                }, is_string($val['only']) ? explode(",", $val['only']) : $val['only']))) {
                    continue;
                    //如果有设置except属性,且当前操作在$val['except']里面,说明当前操作不需要该中间件,跳过
                } elseif (isset($val['except']) && in_array($this->request->action(true), array_map(function ($item) {
                    return strtolower($item);
                }, is_string($val['except']) ? explode(',', $val['except']) : $val['except']))) {
                    continue;
                } else {
                    //保存中间件名称或者类
                    $val = $key;
                }
            }

            if (is_string($val) && strpos($val, ':')) {\
                $val = explode(':', $val, 2);\
            }

            //注册控制器中间件,跟前面注册路由中间件一样原理,只是,中间件的type为controller
            $this->app->middleware->controller($val);
        }
    }
}

分析详见注释。

总结

长路漫漫,到这里,终于分析完了runWithRequest方法,前面的分析,基本是围绕着runWithRequest方法展开。现在,让我们将目光转回Http类的run方法:

public function run(Request $request = null): Response
{
    .
    .
    .
    try {
        $response = $this->runWithRequest($request);
    } catch (Throwable $e) {
        $this->reportException($e);

        $response = $this->renderException($request, $e);
    }
    // 设置cookie并返回响应对象
    return $response;
}

runWithRequest方法跑完,run方法也差不多结束了,最终它返回一个Response对象。

本作品采用《CC 协议》,转载必须注明作者和本文链接
Was mich nicht umbringt, macht mich stärker
讨论数量: 1

写得很清楚 :+1:

4年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!