thinkphp5 路由分发原理


Posted in PHP onMarch 18, 2021

这里以登陆接口为例

请求路由:http://localhost/login

route.php配置

//登陆
Route::post('login','atsystem/login/save');

1.先由入口index.php进来

由public/index.php -> thinkphp/start.php

看下start.php

<?php
namespace think;

// ThinkPHP 引导文件
// 1. 加载基础文件
require __DIR__ . '/base.php';

// 2. 执行应用
App::run()->send();

APP::run()->send(),打开文件thinkphp\library\think\App.php先看下run()函数


    /**
     * 执行应用程序
     * @access public
     * @param  Request $request 请求对象
     * @return Response
     * @throws Exception
     */
    public static function run(Request $request = null)
    {
        $request = is_null($request) ? Request::instance() : $request;

        try {
            $config = self::initCommon();

            // 模块/控制器绑定
            if (defined('BIND_MODULE')) {
                BIND_MODULE && Route::bind(BIND_MODULE);
            } elseif ($config['auto_bind_module']) {
                // 入口自动绑定
                $name = pathinfo($request->baseFile(), PATHINFO_FILENAME);
                if ($name && 'index' != $name && is_dir(APP_PATH . $name)) {
                    Route::bind($name);
                }
            }

            $request->filter($config['default_filter']);

            // 默认语言
            Lang::range($config['default_lang']);
            // 开启多语言机制 检测当前语言
            $config['lang_switch_on'] && Lang::detect();
            $request->langset(Lang::range());

            // 加载系统语言包
            Lang::load([
                THINK_PATH . 'lang' . DS . $request->langset() . EXT,
                APP_PATH . 'lang' . DS . $request->langset() . EXT,
            ]);

            // 监听 app_dispatch
            Hook::listen('app_dispatch', self::$dispatch);
            // 获取应用调度信息
            $dispatch = self::$dispatch;

            // 未设置调度信息则进行 URL 路由检测
            if (empty($dispatch)) {
                $dispatch = self::routeCheck($request, $config);
            }

            // 记录当前调度信息
            $request->dispatch($dispatch);

            // 记录路由和请求信息
            if (self::$debug) {
                Log::record('[ ROUTE ] ' . var_export($dispatch, true), 'info');
                Log::record('[ HEADER ] ' . var_export($request->header(), true), 'info');
                Log::record('[ PARAM ] ' . var_export($request->param(), true), 'info');
            }

            // 监听 app_begin
            Hook::listen('app_begin', $dispatch);

            // 请求缓存检查
            $request->cache(
                $config['request_cache'],
                $config['request_cache_expire'],
                $config['request_cache_except']
            );

            $data = self::exec($dispatch, $config);
        } catch (HttpResponseException $exception) {
            $data = $exception->getResponse();
        }

        // 清空类的实例化
        Loader::clearInstance();

        // 输出数据到客户端
        if ($data instanceof Response) {
            $response = $data;
        } elseif (!is_null($data)) {
            // 默认自动识别响应输出类型
            $type = $request->isAjax() ?
            Config::get('default_ajax_return') :
            Config::get('default_return_type');

            $response = Response::create($data, $type);
        } else {
            $response = Response::create();
        }

        // 监听 app_end
        Hook::listen('app_end', $response);

        return $response;
    }

其中有一行是

$dispatch = self::routeCheck($request, $config);这是路由检测,我们去看下这个方法
  /**
     * URL路由检测(根据PATH_INFO)
     * @access public
     * @param  \think\Request $request 请求实例
     * @param  array          $config  配置信息
     * @return array
     * @throws \think\Exception
     */
    public static function routeCheck($request, array $config)
    {
        $path   = $request->path();
        $depr   = $config['pathinfo_depr'];
        $result = false;

        // 路由检测
        $check = !is_null(self::$routeCheck) ? self::$routeCheck : $config['url_route_on'];
        if ($check) {
            // 开启路由
            if (is_file(RUNTIME_PATH . 'route.php')) {
                // 读取路由缓存
                $rules = include RUNTIME_PATH . 'route.php';
                is_array($rules) && Route::rules($rules);
            } else {
                $files = $config['route_config_file'];
                foreach ($files as $file) {
                    if (is_file(CONF_PATH . $file . CONF_EXT)) {
                        // 导入路由配置
                        $rules = include CONF_PATH . $file . CONF_EXT;
                        is_array($rules) && Route::import($rules);
                    }
                }
            }

            // 路由检测(根据路由定义返回不同的URL调度)
            $result = Route::check($request, $path, $depr, $config['url_domain_deploy']);
            $must   = !is_null(self::$routeMust) ? self::$routeMust : $config['url_route_must'];

            if ($must && false === $result) {
                // 路由无效
                throw new RouteNotFoundException();
            }
        }

        // 路由无效 解析模块/控制器/操作/参数... 支持控制器自动搜索
        if (false === $result) {
            $result = Route::parseUrl($path, $depr, $config['controller_auto_search']);
        }
        return $result;
    }

看见没有,这里去引入了我们的路由配置文件route.php

我们打印下这里返回得$result 返回下面这样得格式

array(3) {
  ["type"] => string(6) "module"
  ["module"] => array(3) {
    [0] => string(8) "atsystem"
    [1] => string(5) "login"
    [2] => string(4) "save"
  }
  ["convert"] => bool(false)
}

然后我们在回到run()方法,其中有一行

$data = self::exec($dispatch, $config);

我们再去看看本类的exec()方法

    /**
     * 执行调用分发
     * @access protected
     * @param array $dispatch 调用信息
     * @param array $config   配置信息
     * @return Response|mixed
     * @throws \InvalidArgumentException
     */
    protected static function exec($dispatch, $config)
    {
        switch ($dispatch['type']) {
            case 'redirect': // 重定向跳转
                $data = Response::create($dispatch['url'], 'redirect')
                    ->code($dispatch['status']);
                break;
            case 'module': // 模块/控制器/操作
                $data = self::module(
                    $dispatch['module'],
                    $config,
                    isset($dispatch['convert']) ? $dispatch['convert'] : null
                );
                break;
            case 'controller': // 执行控制器操作
                $vars = array_merge(Request::instance()->param(), $dispatch['var']);
                $data = Loader::action(
                    $dispatch['controller'],
                    $vars,
                    $config['url_controller_layer'],
                    $config['controller_suffix']
                );
                break;
            case 'method': // 回调方法
                $vars = array_merge(Request::instance()->param(), $dispatch['var']);
                $data = self::invokeMethod($dispatch['method'], $vars);
                break;
            case 'function': // 闭包
                $data = self::invokeFunction($dispatch['function']);
                break;
            case 'response': // Response 实例
                $data = $dispatch['response'];
                break;
            default:
                throw new \InvalidArgumentException('dispatch type not support');
        }

        return $data;
    }

根据我们以上$dispatch返回得数据我们选择

  case 'module': // 模块/控制器/操作
                $data = self::module(
                    $dispatch['module'],
                    $config,
                    isset($dispatch['convert']) ? $dispatch['convert'] : null
                );
                break;

这里调用了本类的module()这个方法,我们去看下

    /**
     * 执行模块
     * @access public
     * @param array $result  模块/控制器/操作
     * @param array $config  配置参数
     * @param bool  $convert 是否自动转换控制器和操作名
     * @return mixed
     * @throws HttpException
     */
    public static function module($result, $config, $convert = null)
    {
        if (is_string($result)) {
            $result = explode('/', $result);
        }

        $request = Request::instance();

        if ($config['app_multi_module']) {
            // 多模块部署
            $module    = strip_tags(strtolower($result[0] ?: $config['default_module']));
            $bind      = Route::getBind('module');
            $available = false;

            if ($bind) {
                // 绑定模块
                list($bindModule) = explode('/', $bind);

                if (empty($result[0])) {
                    $module    = $bindModule;
                    $available = true;
                } elseif ($module == $bindModule) {
                    $available = true;
                }
            } elseif (!in_array($module, $config['deny_module_list']) && is_dir(APP_PATH . $module)) {
                $available = true;
            }

            // 模块初始化
            if ($module && $available) {
                // 初始化模块
                $request->module($module);
                $config = self::init($module);

                // 模块请求缓存检查
                $request->cache(
                    $config['request_cache'],
                    $config['request_cache_expire'],
                    $config['request_cache_except']
                );
            } else {
                throw new HttpException(404, 'module not exists:' . $module);
            }
        } else {
            // 单一模块部署
            $module = '';
            $request->module($module);
        }

        // 设置默认过滤机制
        $request->filter($config['default_filter']);

        // 当前模块路径
        App::$modulePath = APP_PATH . ($module ? $module . DS : '');

        // 是否自动转换控制器和操作名
        $convert = is_bool($convert) ? $convert : $config['url_convert'];

        // 获取控制器名
        $controller = strip_tags($result[1] ?: $config['default_controller']);
        $controller = $convert ? strtolower($controller) : $controller;

        // 获取操作名
        $actionName = strip_tags($result[2] ?: $config['default_action']);
        if (!empty($config['action_convert'])) {
            $actionName = Loader::parseName($actionName, 1);
        } else {
            $actionName = $convert ? strtolower($actionName) : $actionName;
        }

        // 设置当前请求的控制器、操作
        $request->controller(Loader::parseName($controller, 1))->action($actionName);

        // 监听module_init
        Hook::listen('module_init', $request);

        try {
            $instance = Loader::controller(
                $controller,
                $config['url_controller_layer'],
                $config['controller_suffix'],
                $config['empty_controller']
            );
        } catch (ClassNotFoundException $e) {
            throw new HttpException(404, 'controller not exists:' . $e->getClass());
        }

        // 获取当前操作名
        $action = $actionName . $config['action_suffix'];

        $vars = [];
        if (is_callable([$instance, $action])) {
            // 执行操作方法
            $call = [$instance, $action];
            // 严格获取当前操作方法名
            $reflect    = new \ReflectionMethod($instance, $action);
            $methodName = $reflect->getName();
            $suffix     = $config['action_suffix'];
            $actionName = $suffix ? substr($methodName, 0, -strlen($suffix)) : $methodName;
            $request->action($actionName);

        } elseif (is_callable([$instance, '_empty'])) {
            // 空操作
            $call = [$instance, '_empty'];
            $vars = [$actionName];
        } else {
            // 操作不存在
            throw new HttpException(404, 'method not exists:' . get_class($instance) . '->' . $action . '()');
        }

        Hook::listen('action_begin', $call);

        return self::invokeMethod($call, $vars);
    }

看下最后一行的$call ,我们打印下看看数据

array(2) {
  [0] => object(app\atsystem\controller\Login)#5 (5) {
    ["view":protected] => object(think\View)#6 (3) {
      ["engine"] => object(think\view\driver\Think)#7 (2) {
        ["template":"think\view\driver\Think":private] => object(think\Template)#8 (5) {
          ["data":protected] => array(0) {
          }
          ["config":protected] => array(30) {
            ["view_path"] => string(60) "E:\xampp\htdocs\atAdmin\public/../application/atsystem/view/"
            ["view_base"] => string(0) ""
            ["view_suffix"] => string(4) "html"
            ["view_depr"] => string(1) "/"
            ["cache_suffix"] => string(3) "php"
            ["tpl_deny_func_list"] => string(9) "echo,exit"
            ["tpl_deny_php"] => bool(false)
            ["tpl_begin"] => string(2) "\{"
            ["tpl_end"] => string(2) "\}"
            ["strip_space"] => bool(false)
            ["tpl_cache"] => bool(true)
            ["compile_type"] => string(4) "file"
            ["cache_prefix"] => string(0) ""
            ["cache_time"] => int(0)
            ["layout_on"] => bool(false)
            ["layout_name"] => string(6) "layout"
            ["layout_item"] => string(13) "{__CONTENT__}"
            ["taglib_begin"] => string(2) "\{"
            ["taglib_end"] => string(2) "\}"
            ["taglib_load"] => bool(true)
            ["taglib_build_in"] => string(2) "cx"
            ["taglib_pre_load"] => string(0) ""
            ["display_cache"] => bool(false)
            ["cache_id"] => string(0) ""
            ["tpl_replace_string"] => array(0) {
            }
            ["tpl_var_identify"] => string(5) "array"
            ["cache_path"] => string(37) "E:\xampp\htdocs\atAdmin/runtime/temp/"
            ["auto_rule"] => int(1)
            ["taglib_begin_origin"] => string(1) "{"
            ["taglib_end_origin"] => string(1) "}"
          }
          ["literal":"think\Template":private] => array(0) {
          }
          ["includeFile":"think\Template":private] => array(0) {
          }
          ["storage":protected] => object(think\template\driver\File)#9 (1) {
            ["cacheFile":protected] => NULL
          }
        }
        ["config":protected] => array(10) {
          ["view_base"] => string(0) ""
          ["view_path"] => string(60) "E:\xampp\htdocs\atAdmin\public/../application/atsystem/view/"
          ["view_suffix"] => string(4) "html"
          ["view_depr"] => string(1) "/"
          ["tpl_cache"] => bool(true)
          ["auto_rule"] => int(1)
          ["tpl_begin"] => string(1) "{"
          ["tpl_end"] => string(1) "}"
          ["taglib_begin"] => string(1) "{"
          ["taglib_end"] => string(1) "}"
        }
      }
      ["data":protected] => array(0) {
      }
      ["replace":protected] => array(5) {
        ["__ROOT__"] => string(0) ""
        ["__URL__"] => string(15) "/atsystem/login"
        ["__STATIC__"] => string(7) "/static"
        ["__CSS__"] => string(11) "/static/css"
        ["__JS__"] => string(10) "/static/js"
      }
    }
    ["request":protected] => object(think\Request)#2 (33) {
      ["method":protected] => string(4) "POST"
      ["domain":protected] => NULL
      ["url":protected] => string(6) "/login"
      ["baseUrl":protected] => NULL
      ["baseFile":protected] => string(10) "/index.php"
      ["root":protected] => string(0) ""
      ["pathinfo":protected] => string(5) "login"
      ["path":protected] => string(5) "login"
      ["routeInfo":protected] => array(4) {
        ["rule"] => array(1) {
          [0] => string(5) "login"
        }
        ["route"] => string(19) "atsystem/login/save"
        ["option"] => array(0) {
        }
        ["var"] => array(0) {
        }
      }
      ["env":protected] => NULL
      ["dispatch":protected] => array(3) {
        ["type"] => string(6) "module"
        ["module"] => array(3) {
          [0] => string(8) "atsystem"
          [1] => string(5) "login"
          [2] => string(4) "save"
        }
        ["convert"] => bool(false)
      }
      ["module":protected] => string(8) "atsystem"
      ["controller":protected] => string(5) "Login"
      ["action":protected] => string(4) "save"
      ["langset":protected] => string(5) "zh-cn"
      ["param":protected] => array(2) {
        ["username"] => string(11) "wanghuilong"
        ["password"] => string(7) "a123456"
      }
      ["get":protected] => array(0) {
      }
      ["post":protected] => array(2) {
        ["username"] => string(11) "wanghuilong"
        ["password"] => string(7) "a123456"
      }
      ["request":protected] => array(0) {
      }
      ["route":protected] => array(0) {
      }
      ["put":protected] => NULL
      ["session":protected] => array(0) {
      }
      ["file":protected] => array(0) {
      }
      ["cookie":protected] => array(0) {
      }
      ["server":protected] => array(0) {
      }
      ["header":protected] => array(9) {
        ["host"] => string(16) "192.168.1.158:83"
        ["connection"] => string(10) "keep-alive"
        ["content-length"] => string(2) "37"
        ["accept"] => string(16) "application/json"
        ["origin"] => string(51) "chrome-extension://ikceelgnkcigfiacnjdkdejkdicmlibb"
        ["user-agent"] => string(109) "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36"
        ["content-type"] => string(48) "application/x-www-form-urlencoded; charset=UTF-8"
        ["accept-encoding"] => string(13) "gzip, deflate"
        ["accept-language"] => string(14) "zh-CN,zh;q=0.9"
      }
      ["mimeType":protected] => array(12) {
        ["xml"] => string(42) "application/xml,text/xml,application/x-xml"
        ["json"] => string(62) "application/json,text/x-json,application/jsonrequest,text/json"
        ["js"] => string(63) "text/javascript,application/javascript,application/x-javascript"
        ["css"] => string(8) "text/css"
        ["rss"] => string(19) "application/rss+xml"
        ["yaml"] => string(28) "application/x-yaml,text/yaml"
        ["atom"] => string(20) "application/atom+xml"
        ["pdf"] => string(15) "application/pdf"
        ["text"] => string(10) "text/plain"
        ["image"] => string(71) "image/png,image/jpg,image/jpeg,image/pjpeg,image/gif,image/webp,image/*"
        ["csv"] => string(8) "text/csv"
        ["html"] => string(35) "text/html,application/xhtml+xml,*/*"
      }
      ["content":protected] => NULL
      ["filter":protected] => string(0) ""
      ["bind":protected] => array(0) {
      }
      ["input":protected] => string(37) "username=wanghuilong&password=a123456"
      ["cache":protected] => NULL
      ["isCheckCache":protected] => NULL
    }
    ["failException":protected] => bool(false)
    ["batchValidate":protected] => bool(false)
    ["beforeActionList":protected] => array(0) {
    }
  }
  [1] => string(4) "save"
}

这个数组总共两个元素,第一个元素是对象,他将我们传过来模块、控制器组合成了命名空间的格:app\atsystem\controller\Login,第二个元素是我们最后调用的方法save

所以我们重点看下第一个元素是怎么来的

看下module方法中的$call = [$instance, $action];那$instance是怎么来的呢

 try {
            $instance = Loader::controller(
                $controller,
                $config['url_controller_layer'],
                $config['controller_suffix'],
                $config['empty_controller']
            );
        } catch (ClassNotFoundException $e) {
            throw new HttpException(404, 'controller not exists:' . $e->getClass());
        }

我们再去看下thinkphp\library\think\Loader.php里面的controller这个静态类

  /**
     * 实例化(分层)控制器 格式:[模块名/]控制器名
     * @access public
     * @param  string $name         资源地址
     * @param  string $layer        控制层名称
     * @param  bool   $appendSuffix 是否添加类名后缀
     * @param  string $empty        空控制器名称
     * @return object
     * @throws ClassNotFoundException
     */
    public static function controller($name, $layer = 'controller', $appendSuffix = false, $empty = '')
    {
        list($module, $class) = self::getModuleAndClass($name, $layer, $appendSuffix);
  
        if (class_exists($class)) {
            return App::invokeClass($class);
        }

        if ($empty) {
            $emptyClass = self::parseClass($module, $layer, $empty, $appendSuffix);

            if (class_exists($emptyClass)) {
                return new $emptyClass(Request::instance());
            }
        }

        throw new ClassNotFoundException('class not exists:' . $class, $class);
    }

我们打印下第一行看看结果 halt(self::getModuleAndClass($name, $layer, $appendSuffix));

array(2) {
  [0] => string(8) "atsystem"
  [1] => string(29) "app\atsystem\controller\Login"
}

就是这个函数去解析的

大致就是这个意思吧,再看下去头快晕了,这里主要就是看下哪里以调用的路由配置文件route.php,然后在哪里解析成namespace的格式让框架认识这个路由

PHP 相关文章推荐
用PHP制作静态网站的模板框架(一)
Oct 09 PHP
解决163/sohu/sina不能够收到PHP MAIL函数发出邮件的问题
Mar 13 PHP
深入理解ob_flush和flush的区别(ob_flush()与flush()使用方法)
Feb 06 PHP
解析PHP汉字转换拼音的类
Jun 18 PHP
VB中的RasEnumConnections函数返回632错误解决方法
Jul 29 PHP
10个实用的PHP正则表达式汇总
Oct 23 PHP
详解php魔术方法(Magic methods)的使用方法
Feb 14 PHP
PHP学习笔记之php文件操作
Jun 03 PHP
一个实用的php验证码类
Jul 06 PHP
使用PHPUnit进行单元测试并生成代码覆盖率报告的方法
Mar 08 PHP
TP5框架简单登录功能实现方法示例
Oct 31 PHP
php高性能日志系统 seaslog 的安装与使用方法分析
Feb 29 PHP
is_file和file_exists效率比较
Mar 14 #PHP
宝塔面板出现“open_basedir restriction in effect. ”的解决方法
open_basedir restriction in effect. 原因与解决方法
Mar 14 #PHP
aec加密 php_php aes加密解密类(兼容php5、php7)
Mar 14 #PHP
PHP配置文件php.ini中打开错误报告的设置方法
Jan 09 #PHP
imagettftext() 失效,不起作用
Mar 09 #PHP
PHP高并发和大流量解决方案整理
Mar 09 #PHP
You might like
超强分页类2.0发布,支持自定义风格,默认4种显示模式
2007/01/02 PHP
PHP句法规则详解 入门学习
2011/11/09 PHP
php gzip压缩输出的实现方法
2013/04/27 PHP
功能强大的PHP发邮件类
2016/08/29 PHP
laravel中Redis队列监听中断的分析
2020/09/14 PHP
JavaScript中令你抓狂的魔术变量
2006/11/30 Javascript
jQuery select控制插件
2009/08/17 Javascript
jQuery在vs2008及js文件中的无智能提示的解决方法
2010/12/30 Javascript
JS与C#编码解码
2013/12/03 Javascript
高性能JavaScript 重排与重绘(2)
2015/08/11 Javascript
Clipboard.js 无需Flash的JavaScript复制粘贴库
2015/10/02 Javascript
使用CoffeeScrip优美方式编写javascript代码
2015/10/28 Javascript
jquery ui dialog替代confirm实例分析
2016/01/25 Javascript
Angular.js中ng-include用法及多标签页面的实现方式详解
2017/05/07 Javascript
JavaScript实现的原生态兼容IE6可调可控滚动文字功能详解
2017/09/19 Javascript
Angular移动端页面input无法输入的解决方法
2017/11/14 Javascript
Vue源码探究之虚拟节点的实现
2019/04/17 Javascript
浅谈react-router@4.0 使用方法和源码分析
2019/06/04 Javascript
详解JavaScript 作用域
2020/07/14 Javascript
jQuery实现异步上传一个或多个文件
2020/08/17 jQuery
vue在图片上传的时候压缩图片
2020/11/18 Vue.js
Nest.js环境变量配置与序列化详解
2021/02/21 Javascript
python魔法方法-自定义序列详解
2016/07/21 Python
linux平台使用Python制作BT种子并获取BT种子信息的方法
2017/01/20 Python
Python使用matplotlib的pie函数绘制饼状图功能示例
2018/01/08 Python
Python如何测试stdout输出
2020/08/10 Python
PyTorch如何搭建一个简单的网络
2020/08/24 Python
一款css实现的鼠标经过按钮的特效
2014/09/11 HTML / CSS
通过一张图教会你CSS3倒影的实现
2017/09/26 HTML / CSS
美国第二大连锁书店:Books-A-Million
2017/12/28 全球购物
几个人围成一圈的问题
2013/09/26 面试题
我的小天地教学反思
2014/04/30 职场文书
学习“七一”讲话精神体会
2014/07/08 职场文书
学雷锋倡议书
2015/01/19 职场文书
八月一日观后感
2015/06/10 职场文书
大学生创业计划书
2019/06/24 职场文书