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 相关文章推荐
一个可以删除字符串中HTML标记的PHP函数
Oct 09 PHP
一步一步学习PHP(3) php 函数
Feb 15 PHP
php SQL Injection with MySQL
Feb 27 PHP
PHP数组交集的优化代码分析
Mar 06 PHP
TMDPHP 模板引擎使用教程
Mar 13 PHP
PHP 的异常处理、错误的抛出及回调函数等面向对象的错误处理方法
Dec 07 PHP
PHP 使用memcached简单示例分享
Mar 05 PHP
Win7下手动安装apache2.2、php5.4笔记
Apr 03 PHP
php微信开发之批量生成带参数的二维码
Jun 26 PHP
PHP封装的数据库保存session功能类
Jul 11 PHP
php die()与exit()的区别实例详解
Dec 03 PHP
ThinkPHP5框架中使用JWT的方法示例
Jun 03 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
ExtJS与PHP、MySQL实现存储的方法
2010/04/02 PHP
zend framework文件上传功能实例代码
2013/12/25 PHP
PHP实现十进制数字与二十六进制字母串相互转换操作示例
2018/08/10 PHP
对php 判断http还是https,以及获得当前url的方法详解
2019/01/15 PHP
PHP PDOStatement::bindColumn讲解
2019/01/30 PHP
ThinkPHP3.2.3框架邮件发送功能图文实例详解
2019/04/23 PHP
风吟的小型JavaScirpt库 (FY.JS).
2010/03/09 Javascript
jquery实现商品拖动选择效果代码(自写)
2013/05/28 Javascript
解决JQeury显示内容没有边距内容紧挨着浏览器边线
2013/12/20 Javascript
js/jQuery简单实现选项卡功能
2014/01/02 Javascript
JavaScript“尽快失败”的原则实例详解
2016/10/08 Javascript
JavaScript设计模式之代理模式详解
2017/06/09 Javascript
jQuery+SpringMVC中的复选框选择与传值实例
2018/01/08 jQuery
解决Layui选择全部,换页checkbox复选框重新勾选的问题方法
2018/08/14 Javascript
React中使用async validator进行表单验证的实例代码
2018/08/17 Javascript
微信小程序实现圆形进度条动画
2020/11/18 Javascript
[00:34]TI7不朽珍藏III——纯金地穴编织者饰品展示
2017/07/15 DOTA
Python用GET方法上传文件
2015/03/10 Python
详解Python各大聊天系统的屏蔽脏话功能原理
2016/12/01 Python
Python numpy.array()生成相同元素数组的示例
2018/11/12 Python
对pandas写入读取h5文件的方法详解
2018/12/28 Python
python set内置函数的具体使用
2019/07/02 Python
Pandas DataFrame求差集的示例代码
2020/12/13 Python
美国体育用品商店:Paragon Sports
2017/10/08 全球购物
韩国最大的购物网站:Gmarket
2019/06/20 全球购物
阿里巴巴英国:Alibaba英国
2019/12/11 全球购物
应届生幼儿园求职信
2013/11/12 职场文书
会计电算化专业毕业生求职信范文
2013/12/10 职场文书
大学生村官典型材料
2014/01/12 职场文书
最新奶茶店创业计划书
2014/01/25 职场文书
小学教师节活动方案
2014/01/31 职场文书
军训拉歌口号
2014/06/13 职场文书
学生不讲诚信检讨书
2014/09/29 职场文书
学校食堂食品安全承诺书
2015/04/29 职场文书
圣诞晚会主持词
2015/07/01 职场文书
2015年初中教师个人工作总结
2015/07/21 职场文书