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
PHP:风雨欲来 路在何方?
Oct 09 PHP
使用PHP 5.0创建图形的巧妙方法
Oct 12 PHP
PHP比你想象的好得多
Nov 27 PHP
php多次include后导致全局变量global失效的解决方法
Feb 28 PHP
Twig模板引擎用法入门教程
Jan 20 PHP
php实现的一段简单概率相关代码
May 30 PHP
PHP 常用时间函数资料整理
Oct 22 PHP
解决微信授权回调页面域名只能设置一个的问题
Dec 11 PHP
Yii2结合Workerman的websocket示例详解
Sep 10 PHP
PHP yield关键字功能与用法分析
Jan 03 PHP
Laravel框架运行出错提示RuntimeException No application encryption key has been specified.解决方法
Apr 02 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
全国FM电台频率大全 - 17 湖北省
2020/03/11 无线电
人族 TERRAN 概述
2020/03/14 星际争霸
phpfans留言版用到的数据操作类和分页类
2007/01/04 PHP
Admin generator, filters and I18n
2011/10/06 PHP
自定义php类(查找/修改)xml文档
2013/03/26 PHP
php+redis消息队列实现抢购功能
2018/02/08 PHP
PDO::inTransaction讲解
2019/01/28 PHP
用Javascript评估用户输入密码的强度(Knockout版)
2011/11/30 Javascript
javascript基于DOM实现权限选择实例分析
2015/05/14 Javascript
JavaScript中字符串拼接的基本方法
2015/07/07 Javascript
JavaScript中this详解
2015/09/01 Javascript
jquery实现美观的导航菜单鼠标提示特效代码
2015/09/06 Javascript
jQuery多级手风琴菜单实例讲解
2015/10/22 Javascript
jquery.cookie实现的客户端购物车操作实例
2015/12/24 Javascript
浅谈js数据类型判断与数组判断
2016/08/29 Javascript
Bootstrap CSS组件之分页(pagination)和翻页(pager)
2016/12/17 Javascript
使用bootstrap-paginator.js 分页来进行ajax 异步分页请求示例
2017/03/09 Javascript
js模仿微信朋友圈计算时间显示几天/几小时/几分钟/几秒之前
2017/04/27 Javascript
浅谈webpack编译vue项目生成的代码探索
2017/12/11 Javascript
JavaScript递归函数定义与用法实例分析
2019/01/24 Javascript
[00:36]DOTA2风云人物相约完美“圣”典 12月17日不见不散
2016/11/30 DOTA
[01:00:17]DOTA2-DPC中国联赛 正赛 SAG vs Dynasty BO3 第二场 1月25日
2021/03/11 DOTA
Python利用字典将两个通讯录文本合并为一个文本实例
2018/01/16 Python
python基于递归解决背包问题详解
2019/07/03 Python
超实用的 30 段 Python 案例
2019/10/10 Python
使用pytorch和torchtext进行文本分类的实例
2020/01/08 Python
一款超酷的js+css3实现的3D标签云特效兼容ie7/8/9
2013/11/18 HTML / CSS
adidas官方旗舰店:德国运动用品制造商
2017/11/25 全球购物
sealed修饰符是干什么的
2012/10/23 面试题
物流仓管员岗位职责
2013/12/04 职场文书
竞聘上岗演讲稿范文
2014/01/10 职场文书
区域销售主管岗位职责
2014/06/15 职场文书
民主评议党员登记表自我评价
2014/10/20 职场文书
乡镇科协工作总结2015
2015/05/19 职场文书
2019最新劳动仲裁申请书!
2019/07/08 职场文书
分析Netty直接内存原理及应用
2021/06/14 Java/Android