thinkPHP 路由实现分享

421 阅读2分钟

公司内部系统代码重构使用了 thinkPHP5.0 ,在使用tp框架时,就学习了tp 框架的路由实现。

一、 PATH_INFO 模式是什么?

PATH_INFO 模式是伪静态的一种。

我们可以使用 PATH_INFO 来代替 Rewrite 来实现伪静态页面, 另外不少 PHP 框架也使用PATH_INFO 来作为路由载体

伪静态页面是静态 URL 与动态 URL 互通的一个桥梁,它是指动态网址通过 URL 重写的手段去掉其动态参数,使 URL 静态化,但在实际的网页目录中并没有重写 URL。

简单来说,伪静态 URL 就是通过服务器转换伪装文件名或地址,使该页面类似于静态页面,但服务器上没有独立存在的文件,其本质还是动态页面。

PATH_INFO是服务器状态中的一个参数,PHP通过$_SERVER[‘PATH_INFO’]可以查看内容。

二、 thinkPHP 路由源码分析

image.png

thinkPHP 源码首先监听调度信息,发起调度请求;并记录请求信息

    // 监听app_dispatch
    $this->hook->listen('app_dispatch');

    $dispatch = $this->dispatch;

    if (empty($dispatch)) {
        // 路由检测
        $dispatch = $this->routeCheck()->init();
    }

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

    // 记录路由和请求信息
    if ($this->appDebug) {
        $this->log('[ ROUTE ] ' . var_export($this->request->routeInfo(), true));
        $this->log('[ HEADER ] ' . var_export($this->request->header(), true));
        $this->log('[ PARAM ] ' . var_export($this->request->param(), true));
    }

下面分享一些,相关的路由方法

  1. 获取 pathinfo
 /**
     * 获取当前请求URL的pathinfo信息(含URL后缀)
     * @access public
     * @return string
     */
    public function pathinfo()
    {
        if (is_null($this->pathinfo)) {
            if (isset($_GET[$this->config['var_pathinfo']])) {
                // 判断URL里面是否有兼容模式参数
                $pathinfo = $_GET[$this->config['var_pathinfo']];
                unset($_GET[$this->config['var_pathinfo']]);
                unset($this->get[$this->config['var_pathinfo']]);
            } elseif ($this->isCli()) {
                // CLI模式下 index.php module/controller/action/params/...
                $pathinfo = isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : '';
            } elseif ('cli-server' == PHP_SAPI) {
                $pathinfo = strpos($this->server('REQUEST_URI'), '?') ? strstr($this->server('REQUEST_URI'), '?', true) : $this->server('REQUEST_URI');
            } elseif ($this->server('PATH_INFO')) {
                $pathinfo = $this->server('PATH_INFO');
            }

            // 分析PATHINFO信息
            if (!isset($pathinfo)) {
                foreach ($this->config['pathinfo_fetch'] as $type) {
                    if ($this->server($type)) {
                        $pathinfo = (0 === strpos($this->server($type), $this->server('SCRIPT_NAME'))) ?
                        substr($this->server($type), strlen($this->server('SCRIPT_NAME'))) : $this->server($type);
                        break;
                    }
                }
            }

            if (!empty($pathinfo)) {
                unset($this->get[$pathinfo], $this->request[$pathinfo]);
            }

            $this->pathinfo = empty($pathinfo) || '/' == $pathinfo ? '' : ltrim($pathinfo, '/');
        }

        return $this->pathinfo;
    }
  1. 导入配置文件
/**
     * 导入配置文件的路由规则
     * @access public
     * @param  array     $rules 路由规则
     * @param  string    $type  请求类型
     * @return void
     */
    public function import(array $rules, $type = '*')
    {
        // 检查域名部署
        if (isset($rules['__domain__'])) {
            foreach ($rules['__domain__'] as $key => $rule) {
                $this->domain($key, $rule);
            }
            unset($rules['__domain__']);
        }

        // 检查变量规则
        if (isset($rules['__pattern__'])) {
            $this->pattern($rules['__pattern__']);
            unset($rules['__pattern__']);
        }

        // 检查路由别名
        if (isset($rules['__alias__'])) {
            foreach ($rules['__alias__'] as $key => $val) {
                $this->alias($key, $val);
            }
            unset($rules['__alias__']);
        }

        // 检查资源路由
        if (isset($rules['__rest__'])) {
            foreach ($rules['__rest__'] as $key => $rule) {
                $this->resource($key, $rule);
            }
            unset($rules['__rest__']);
        }

        // 检查路由规则(包含分组)
        foreach ($rules as $key => $val) {
            if (is_numeric($key)) {
                $key = array_shift($val);
            }

            if (empty($val)) {
                continue;
            }

            if (is_string($key) && 0 === strpos($key, '[')) {
                $key = substr($key, 1, -1);
                $this->group($key, $val);
            } elseif (is_array($val)) {
                $this->rule($key, $val[0], $type, $val[1], isset($val[2]) ? $val[2] : []);
            } else {
                $this->rule($key, $val, $type);
            }
        }
    }
  1. 解析路由
/**
     * 解析匹配到的规则路由
     * @access public
     * @param  Request   $request 请求对象
     * @param  string    $rule 路由规则
     * @param  string    $route 路由地址
     * @param  string    $url URL地址
     * @param  array     $option 路由参数
     * @param  array     $matches 匹配的变量
     * @return Dispatch
     */
    public function parseRule($request, $rule, $route, $url, $option = [], $matches = [])
    {
        if (is_string($route) && isset($option['prefix'])) {
            // 路由地址前缀
            $route = $option['prefix'] . $route;
        }

        // 替换路由地址中的变量
        if (is_string($route) && !empty($matches)) {
            $search = $replace = [];

            foreach ($matches as $key => $value) {
                $search[]  = '<' . $key . '>';
                $replace[] = $value;

                $search[]  = ':' . $key;
                $replace[] = $value;
            }

            $route = str_replace($search, $replace, $route);
        }

        // 解析额外参数
        $count = substr_count($rule, '/');
        $url   = array_slice(explode('|', $url), $count + 1);
        $this->parseUrlParams($request, implode('|', $url), $matches);

        $this->vars    = $matches;
        $this->option  = $option;
        $this->doAfter = true;

        // 发起路由调度
        return $this->dispatch($request, $route, $option);
    }
  1. 自动定位模型和类
/**
    * 自动定位控制器类
    * @access protected
    * @param  string    $module 模块名
    * @param  array     $path   URL
    * @return string
    */
   protected function autoFindController($module, &$path)
   {
       $dir    = $this->app->getAppPath() . ($module ? $module . '/' : '') . $this->rule->getConfig('url_controller_layer');
       $suffix = $this->app->getSuffix() || $this->rule->getConfig('controller_suffix') ? ucfirst($this->rule->getConfig('url_controller_layer')) : '';

       $item = [];
       $find = false;

       foreach ($path as $val) {
           $item[] = $val;
           $file   = $dir . '/' . str_replace('.', '/', $val) . $suffix . '.php';
           $file   = pathinfo($file, PATHINFO_DIRNAME) . '/' . Loader::parseName(pathinfo($file, PATHINFO_FILENAME), 1) . '.php';
           if (is_file($file)) {
               $find = true;
               break;
           } else {
               $dir .= '/' . Loader::parseName($val);
           }
       }

       if ($find) {
           $controller = implode('.', $item);
           $path       = array_slice($path, count($item));
       } else {
           $controller = array_shift($path);
       }

       return $controller;
   }