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文件读写操作之文件写入代码
Jan 13 PHP
php调用MySQL存储过程的方法集合(推荐)
Jul 03 PHP
PHP mysql与mysqli事务使用说明 分享
Aug 17 PHP
php实现过滤表单提交中html标签的方法
Oct 17 PHP
PHP类的反射用法实例
Nov 03 PHP
Symfony数据校验方法实例分析
Jan 26 PHP
php实现xml与json之间的相互转换功能实例
Jul 07 PHP
PHP 中 DOMDocument保存xml时中文出现乱码问题的解决方案
Sep 19 PHP
PHP实现判断数组是一维、二维或几维的方法
Feb 06 PHP
PHP实现的折半查找算法示例
Dec 19 PHP
php遍历目录下文件并按修改时间排序操作示例
Jul 12 PHP
PHP实现基本留言板功能原理与步骤详解
Mar 26 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
PHP上传图片进行等比缩放可增加水印功能
2014/01/13 PHP
php大小写转换函数(strtolower、strtoupper)用法介绍
2017/11/17 PHP
ThinkPHP整合datatables实现服务端分页的示例代码
2018/02/10 PHP
js实现按钮加背景图片常用方法
2014/11/01 Javascript
javascript实现禁止复制网页内容
2014/12/16 Javascript
jqueryUI里拖拽排序示例分析
2015/02/26 Javascript
TypeScript具有的几个不同特质
2015/04/07 Javascript
javascript中CheckBox全选终极方案
2015/05/20 Javascript
Java中Timer的用法详解
2015/10/21 Javascript
Bootstrap Chart组件使用教程
2016/04/28 Javascript
浅谈Javascript数据属性与访问器属性
2016/07/26 Javascript
JS简单封装的图片无缝滚动效果示例【测试可用】
2017/03/22 Javascript
vue裁切预览组件功能的实现步骤
2018/05/04 Javascript
老生常谈JS中的继承及实现代码
2018/07/06 Javascript
H5+C3+JS实现五子棋游戏(AI篇)
2020/05/28 Javascript
解决iview多表头动态更改列元素发生的错误的方法
2018/11/02 Javascript
微信小程序利用for循环解决内容变更问题
2020/03/05 Javascript
Vue组件间数据传递的方式(3种)
2020/07/13 Javascript
vue项目中使用多选框的实例代码
2020/07/22 Javascript
[01:01:35]Optic vs paiN 2018国际邀请赛小组赛BO2 第二场 8.19
2018/08/21 DOTA
[01:04:05]Mineski vs TNC 2019国际邀请赛小组赛 BO2 第一场 8.15
2019/08/16 DOTA
python实现二分查找算法
2017/09/21 Python
Python numpy 常用函数总结
2017/12/07 Python
解决python写入带有中文的字符到文件错误的问题
2019/01/31 Python
Python3.5面向对象与继承图文实例详解
2019/04/24 Python
使用 Django Highcharts 实现数据可视化过程解析
2019/07/31 Python
Python 项目转化为so文件实例
2019/12/23 Python
Python设计密码强度校验程序
2020/07/30 Python
高级护理专业大学生求职信
2013/10/24 职场文书
求职简历中个人的自我评价
2013/12/01 职场文书
跑操口号
2014/06/12 职场文书
2016中秋节问候语
2015/11/11 职场文书
化工生产实习心得体会
2016/01/22 职场文书
Python Pycharm虚拟下百度飞浆PaddleX安装报错问题及处理方法(亲测100%有效)
2021/05/24 Python
Python将CSV文件转化为HTML文件的操作方法
2021/06/30 Python
详解Mysql数据库平滑扩容解决高并发和大数据量问题
2022/05/25 MySQL