精华内容
下载资源
问答
  • ThinkPHP 6.0原始码解析 时间有限,只是做了初步的初始化原始代码注释,有空持续更新。 已注释文件 ├─sourcecode │ ├─public │ │ └─index.php │ │ │ └─vendor │ └─topthink │ └...
  • tp5底层源码分析之路由解析

    千次阅读 2019-07-14 11:05:28
    5.设置路由规则 /** * 设置路由规则 * @access public * @param string|array $rule 路由规则 * @param string $route 路由地址 * @param string $type 请求类型 * @param array $option 路由参数 * @param...
    • 流程(下载)
      在这里插入图片描述

    • 相关函数
      1.path

     /**
         * 获取当前请求URL的pathinfo信息(不含URL后缀)
         * @access public
         * @return string
         */
        public function path()
        {
    
            if (is_null($this->path)) {
                $suffix   = Config::get('url_html_suffix');
                $pathinfo = $this->pathinfo();
                if (false === $suffix) {
                    // 禁止伪静态访问
                    $this->path = $pathinfo;
                } elseif ($suffix) {
                    // 去除正常的URL后缀
                    $this->path = preg_replace('/\.(' . ltrim($suffix, '.') . ')$/i', '', $pathinfo);
                } else {
                    // 允许任何后缀访问
                    $this->path = preg_replace('/\.' . $this->ext() . '$/i', '', $pathinfo);
                }
            }
            return $this->path;
        }
    
    

    2.pathinfo

    /**
         * 获取当前请求URL的pathinfo信息(含URL后缀)
         * @access public
         * @return string
         */
        public function pathinfo()
        {
            if (is_null($this->pathinfo)) {
                if (isset($_GET[Config::get('var_pathinfo')])) {
                    // 判断URL里面是否有兼容模式参数
                    $_SERVER['PATH_INFO'] = $_GET[Config::get('var_pathinfo')];
                    unset($_GET[Config::get('var_pathinfo')]);
                } elseif (IS_CLI) {
                    // CLI模式下 index.php module/controller/action/params/...
                    $_SERVER['PATH_INFO'] = isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : '';
                }
    
                // 分析PATHINFO信息
                if (!isset($_SERVER['PATH_INFO'])) {
                    foreach (Config::get('pathinfo_fetch') as $type) {
                        if (!empty($_SERVER[$type])) {
                            $_SERVER['PATH_INFO'] = (0 === strpos($_SERVER[$type], $_SERVER['SCRIPT_NAME'])) ?
                            substr($_SERVER[$type], strlen($_SERVER['SCRIPT_NAME'])) : $_SERVER[$type];
                            break;
                        }
                    }
                }
                $this->pathinfo = empty($_SERVER['PATH_INFO']) ? '/' : ltrim($_SERVER['PATH_INFO'], '/');
            }
            return $this->pathinfo;
        }
    
    

    3.导入配置文件

     /**
         * 导入配置文件的路由规则
         * @access public
         * @param array  $rule 路由规则
         * @param string $type 请求类型
         * @return void
         */
        public static function import(array $rule, $type = '*')
        {
            // 检查域名部署
            if (isset($rule['__domain__'])) {
                self::domain($rule['__domain__']);
                unset($rule['__domain__']);
            }
    
            // 检查变量规则
            if (isset($rule['__pattern__'])) {
                self::pattern($rule['__pattern__']);
                unset($rule['__pattern__']);
            }
    
            // 检查路由别名
            if (isset($rule['__alias__'])) {
                self::alias($rule['__alias__']);
                unset($rule['__alias__']);
            }
    
            // 检查资源路由
            if (isset($rule['__rest__'])) {
                self::resource($rule['__rest__']);
                unset($rule['__rest__']);
            }
    
            self::registerRules($rule, strtolower($type));
        }
    

    4.批量注册

     /**
         * 导入配置文件的路由规则
         * @access public
         * @param array  $rule 路由规则
         * @param string $type 请求类型
         * @return void
         */
        public static function import(array $rule, $type = '*')
        {
            // 检查域名部署
            if (isset($rule['__domain__'])) {
                self::domain($rule['__domain__']);
                unset($rule['__domain__']);
            }
    
            // 检查变量规则
            if (isset($rule['__pattern__'])) {
                self::pattern($rule['__pattern__']);
                unset($rule['__pattern__']);
            }
    
            // 检查路由别名
            if (isset($rule['__alias__'])) {
                self::alias($rule['__alias__']);
                unset($rule['__alias__']);
            }
    
            // 检查资源路由
            if (isset($rule['__rest__'])) {
                self::resource($rule['__rest__']);
                unset($rule['__rest__']);
            }
    
            self::registerRules($rule, strtolower($type));
        }
    

    5.设置路由规则

    /**
         * 设置路由规则
         * @access public
         * @param string|array $rule    路由规则
         * @param string       $route   路由地址
         * @param string       $type    请求类型
         * @param array        $option  路由参数
         * @param array        $pattern 变量规则
         * @param string       $group   所属分组
         * @return void
         */
        protected static function setRule($rule, $route, $type = '*', $option = [], $pattern = [], $group = '')
        {
            if (is_array($rule)) {
                $name = $rule[0];
                $rule = $rule[1];
            } elseif (is_string($route)) {
                $name = $route;
            }
            if (!isset($option['complete_match'])) {
                if (Config::get('route_complete_match')) {
                    $option['complete_match'] = true;
                } elseif ('$' == substr($rule, -1, 1)) {
                    // 是否完整匹配
                    $option['complete_match'] = true;
                }
            } elseif (empty($option['complete_match']) && '$' == substr($rule, -1, 1)) {
                // 是否完整匹配
                $option['complete_match'] = true;
            }
    
            if ('$' == substr($rule, -1, 1)) {
                $rule = substr($rule, 0, -1);
            }
    
            if ('/' != $rule || $group) {
                $rule = trim($rule, '/');
            }
            $vars = self::parseVar($rule);
            if (isset($name)) {
                $key    = $group ? $group . ($rule ? '/' . $rule : '') : $rule;
                $suffix = isset($option['ext']) ? $option['ext'] : null;
                self::name($name, [$key, $vars, self::$domain, $suffix]);
            }
            if (isset($option['modular'])) {
                $route = $option['modular'] . '/' . $route;
            }
            if ($group) {
                if ('*' != $type) {
                    $option['method'] = $type;
                }
                if (self::$domain) {
                    self::$rules['domain'][self::$domain]['*'][$group]['rule'][] = ['rule' => $rule, 'route' => $route, 'var' => $vars, 'option' => $option, 'pattern' => $pattern];
                } else {
                    self::$rules['*'][$group]['rule'][] = ['rule' => $rule, 'route' => $route, 'var' => $vars, 'option' => $option, 'pattern' => $pattern];
                }
            } else {
                if ('*' != $type && isset(self::$rules['*'][$rule])) {
                    unset(self::$rules['*'][$rule]);
                }
                if (self::$domain) {
                    self::$rules['domain'][self::$domain][$type][$rule] = ['rule' => $rule, 'route' => $route, 'var' => $vars, 'option' => $option, 'pattern' => $pattern];
                } else {
                    self::$rules[$type][$rule] = ['rule' => $rule, 'route' => $route, 'var' => $vars, 'option' => $option, 'pattern' => $pattern];
                }
                if ('*' == $type) {
                    // 注册路由快捷方式
                    foreach (['get', 'post', 'put', 'delete', 'patch', 'head', 'options'] as $method) {
                        if (self::$domain && !isset(self::$rules['domain'][self::$domain][$method][$rule])) {
                            self::$rules['domain'][self::$domain][$method][$rule] = true;
                        } elseif (!self::$domain && !isset(self::$rules[$method][$rule])) {
                            self::$rules[$method][$rule] = true;
                        }
                    }
                }
            }
        }
    

    6.注册路由分组

     /**
         * 注册路由分组
         * @access public
         * @param string|array   $name    分组名称或者参数
         * @param array|\Closure $routes  路由地址
         * @param array          $option  路由参数
         * @param array          $pattern 变量规则
         * @return void
         */
        public static function group($name, $routes, $option = [], $pattern = [])
        {
            if (is_array($name)) {
                $option = $name;
                $name   = isset($option['name']) ? $option['name'] : '';
            }
            // 分组
            $currentGroup = self::getGroup('name');
            if ($currentGroup) {
                $name = $currentGroup . ($name ? '/' . ltrim($name, '/') : '');
            }
            if (!empty($name)) {
                if ($routes instanceof \Closure) {
                    $currentOption  = self::getGroup('option');
                    $currentPattern = self::getGroup('pattern');
                    self::setGroup($name, array_merge($currentOption, $option), array_merge($currentPattern, $pattern));
                    call_user_func_array($routes, []);
                    self::setGroup($currentGroup, $currentOption, $currentPattern);
                    if ($currentGroup != $name) {
                        self::$rules['*'][$name]['route']   = '';
                        self::$rules['*'][$name]['var']     = self::parseVar($name);
                        self::$rules['*'][$name]['option']  = $option;
                        self::$rules['*'][$name]['pattern'] = $pattern;
                    }
                } else {
                    $item          = [];
                    $completeMatch = Config::get('route_complete_match');
                    foreach ($routes as $key => $val) {
                        if (is_numeric($key)) {
                            $key = array_shift($val);
                        }
                        if (is_array($val)) {
                            $route    = $val[0];
                            $option1  = array_merge($option, isset($val[1]) ? $val[1] : []);
                            $pattern1 = array_merge($pattern, isset($val[2]) ? $val[2] : []);
                        } else {
                            $route = $val;
                        }
    
                        $options  = isset($option1) ? $option1 : $option;
                        $patterns = isset($pattern1) ? $pattern1 : $pattern;
                        if ('$' == substr($key, -1, 1)) {
                            // 是否完整匹配
                            $options['complete_match'] = true;
                            $key                       = substr($key, 0, -1);
                        } elseif ($completeMatch) {
                            $options['complete_match'] = true;
                        }
                        $key    = trim($key, '/');
                        $vars   = self::parseVar($key);
                        $item[] = ['rule' => $key, 'route' => $route, 'var' => $vars, 'option' => $options, 'pattern' => $patterns];
                        // 设置路由标识
                        $suffix = isset($options['ext']) ? $options['ext'] : null;
                        self::name($route, [$name . ($key ? '/' . $key : ''), $vars, self::$domain, $suffix]);
                    }
                    self::$rules['*'][$name] = ['rule' => $item, 'route' => '', 'var' => [], 'option' => $option, 'pattern' => $pattern];
                }
    
                foreach (['get', 'post', 'put', 'delete', 'patch', 'head', 'options'] as $method) {
                    if (!isset(self::$rules[$method][$name])) {
                        self::$rules[$method][$name] = true;
                    } elseif (is_array(self::$rules[$method][$name])) {
                        self::$rules[$method][$name] = array_merge(self::$rules['*'][$name], self::$rules[$method][$name]);
                    }
                }
    
            } elseif ($routes instanceof \Closure) {
                // 闭包注册
                $currentOption  = self::getGroup('option');
                $currentPattern = self::getGroup('pattern');
                self::setGroup('', array_merge($currentOption, $option), array_merge($currentPattern, $pattern));
                call_user_func_array($routes, []);
                self::setGroup($currentGroup, $currentOption, $currentPattern);
            } else {
                // 批量注册路由
                self::rule($routes, '', '*', $option, $pattern);
            }
        }
    
    

    7.解析模块的URL地址

      /**
         * 解析模块的URL地址 [模块/控制器/操作?]参数1=值1&参数2=值2...
         * @access public
         * @param string $url        URL地址
         * @param string $depr       URL分隔符
         * @param bool   $autoSearch 是否自动深度搜索控制器
         * @return array
         */
        public static function parseUrl($url, $depr = '/', $autoSearch = false)
        {
    
            if (isset(self::$bind['module'])) {
                $bind = str_replace('/', $depr, self::$bind['module']);
                // 如果有模块/控制器绑定
                $url = $bind . ('.' != substr($bind, -1) ? $depr : '') . ltrim($url, $depr);
            }
            $url              = str_replace($depr, '|', $url);
            list($path, $var) = self::parseUrlPath($url);
            $route            = [null, null, null];
            if (isset($path)) {
                // 解析模块
                $module = Config::get('app_multi_module') ? array_shift($path) : null;
                if ($autoSearch) {
                    // 自动搜索控制器
                    $dir    = APP_PATH . ($module ? $module . DS : '') . Config::get('url_controller_layer');
                    $suffix = App::$suffix || Config::get('controller_suffix') ? ucfirst(Config::get('url_controller_layer')) : '';
                    $item   = [];
                    $find   = false;
                    foreach ($path as $val) {
                        $item[] = $val;
                        $file   = $dir . DS . str_replace('.', DS, $val) . $suffix . EXT;
                        $file   = pathinfo($file, PATHINFO_DIRNAME) . DS . Loader::parseName(pathinfo($file, PATHINFO_FILENAME), 1) . EXT;
                        if (is_file($file)) {
                            $find = true;
                            break;
                        } else {
                            $dir .= DS . Loader::parseName($val);
                        }
                    }
                    if ($find) {
                        $controller = implode('.', $item);
                        $path       = array_slice($path, count($item));
                    } else {
                        $controller = array_shift($path);
                    }
                } else {
                    // 解析控制器
                    $controller = !empty($path) ? array_shift($path) : null;
                }
                // 解析操作
                $action = !empty($path) ? array_shift($path) : null;
                // 解析额外参数
                self::parseUrlParams(empty($path) ? '' : implode('|', $path));
                // 封装路由
                $route = [$module, $controller, $action];
                // 检查地址是否被定义过路由
                $name  = strtolower($module . '/' . Loader::parseName($controller, 1) . '/' . $action);
                $name2 = '';
                if (empty($module) || isset($bind) && $module == $bind) {
                    $name2 = strtolower(Loader::parseName($controller, 1) . '/' . $action);
                }
    
                if (isset(self::$rules['name'][$name]) || isset(self::$rules['name'][$name2])) {
                    throw new HttpException(404, 'invalid request:' . str_replace('|', $depr, $url));
                }
            }
            return ['type' => 'module', 'module' => $route];
        }
    
    

    8.解析URL的pathinfo参数和变量

     /**
         * 解析URL的pathinfo参数和变量
         * @access private
         * @param string $url URL地址
         * @return array
         */
        private static function parseUrlPath($url)
        {
            // 分隔符替换 确保路由定义使用统一的分隔符
            $url = str_replace('|', '/', $url);
            $url = trim($url, '/');
            $var = [];
            if (false !== strpos($url, '?')) {
                // [模块/控制器/操作?]参数1=值1&参数2=值2...
                $info = parse_url($url);
                $path = explode('/', $info['path']);
                parse_str($info['query'], $var);
            } elseif (strpos($url, '/')) {
                // [模块/控制器/操作]
                $path = explode('/', $url);
            } else {
                $path = [$url];
            }
            return [$path, $var];
        }
    

    9.检测URL和规则路由是否匹配

     /**
         * 检测URL和规则路由是否匹配
         * @access private
         * @param string $url     URL地址
         * @param string $rule    路由规则
         * @param array  $pattern 变量规则
         * @return array|false
         */
        private static function match($url, $rule, $pattern)
        {
            $m2 = explode('/', $rule);
            $m1 = explode('|', $url);
    
            $var = [];
            foreach ($m2 as $key => $val) {
                // val中定义了多个变量 <id><name>
                if (false !== strpos($val, '<') && preg_match_all('/<(\w+(\??))>/', $val, $matches)) {
                    $value   = [];
                    $replace = [];
                    foreach ($matches[1] as $name) {
                        if (strpos($name, '?')) {
                            $name      = substr($name, 0, -1);
                            $replace[] = '(' . (isset($pattern[$name]) ? $pattern[$name] : '\w+') . ')?';
                        } else {
                            $replace[] = '(' . (isset($pattern[$name]) ? $pattern[$name] : '\w+') . ')';
                        }
                        $value[] = $name;
                    }
                    $val = str_replace($matches[0], $replace, $val);
                    if (preg_match('/^' . $val . '$/', isset($m1[$key]) ? $m1[$key] : '', $match)) {
                        array_shift($match);
                        foreach ($value as $k => $name) {
                            if (isset($match[$k])) {
                                $var[$name] = $match[$k];
                            }
                        }
                        continue;
                    } else {
                        return false;
                    }
                }
    
                if (0 === strpos($val, '[:')) {
                    // 可选参数
                    $val      = substr($val, 1, -1);
                    $optional = true;
                } else {
                    $optional = false;
                }
                if (0 === strpos($val, ':')) {
                    // URL变量
                    $name = substr($val, 1);
                    if (!$optional && !isset($m1[$key])) {
                        return false;
                    }
                    if (isset($m1[$key]) && isset($pattern[$name])) {
                        // 检查变量规则
                        if ($pattern[$name] instanceof \Closure) {
                            $result = call_user_func_array($pattern[$name], [$m1[$key]]);
                            if (false === $result) {
                                return false;
                            }
                        } elseif (!preg_match(0 === strpos($pattern[$name], '/') ? $pattern[$name] : '/^' . $pattern[$name] . '$/', $m1[$key])) {
                            return false;
                        }
                    }
                    $var[$name] = isset($m1[$key]) ? $m1[$key] : '';
                } elseif (!isset($m1[$key]) || 0 !== strcasecmp($val, $m1[$key])) {
                    return false;
                }
            }
            // 成功匹配后返回URL中的动态变量数组
            return $var;
        }
    

    10.解析规则路由

    /**
         * 解析规则路由
         * @access private
         * @param string $rule      路由规则
         * @param string $route     路由地址
         * @param string $pathinfo  URL地址
         * @param array  $option    路由参数
         * @param array  $matches   匹配的变量
         * @param bool   $fromCache 通过缓存解析
         * @return array
         */
        private static function parseRule($rule, $route, $pathinfo, $option = [], $matches = [], $fromCache = false)
        {
            $request = Request::instance();
    
            //保存解析缓存
            if (Config::get('route_check_cache') && !$fromCache) {
                try {
                    $key = self::getCheckCacheKey($request);
                    Cache::tag('route_check')->set($key, [$rule, $route, $pathinfo, $option, $matches]);
                } catch (\Exception $e) {
    
                }
            }
    
            // 解析路由规则
            if ($rule) {
                $rule = explode('/', $rule);
                // 获取URL地址中的参数
                $paths = explode('|', $pathinfo);
                foreach ($rule as $item) {
                    $fun = '';
                    if (0 === strpos($item, '[:')) {
                        $item = substr($item, 1, -1);
                    }
                    if (0 === strpos($item, ':')) {
                        $var           = substr($item, 1);
                        $matches[$var] = array_shift($paths);
                    } else {
                        // 过滤URL中的静态变量
                        array_shift($paths);
                    }
                }
            } else {
                $paths = explode('|', $pathinfo);
            }
    
            // 获取路由地址规则
            if (is_string($route) && isset($option['prefix'])) {
                // 路由地址前缀
                $route = $option['prefix'] . $route;
            }
            // 替换路由地址中的变量
            if (is_string($route) && !empty($matches)) {
                foreach ($matches as $key => $val) {
                    if (false !== strpos($route, ':' . $key)) {
                        $route = str_replace(':' . $key, $val, $route);
                    }
                }
            }
    
            // 绑定模型数据
            if (isset($option['bind_model'])) {
                $bind = [];
                foreach ($option['bind_model'] as $key => $val) {
                    if ($val instanceof \Closure) {
                        $result = call_user_func_array($val, [$matches]);
                    } else {
                        if (is_array($val)) {
                            $fields    = explode('&', $val[1]);
                            $model     = $val[0];
                            $exception = isset($val[2]) ? $val[2] : true;
                        } else {
                            $fields    = ['id'];
                            $model     = $val;
                            $exception = true;
                        }
                        $where = [];
                        $match = true;
                        foreach ($fields as $field) {
                            if (!isset($matches[$field])) {
                                $match = false;
                                break;
                            } else {
                                $where[$field] = $matches[$field];
                            }
                        }
                        if ($match) {
                            $query  = strpos($model, '\\') ? $model::where($where) : Loader::model($model)->where($where);
                            $result = $query->failException($exception)->find();
                        }
                    }
                    if (!empty($result)) {
                        $bind[$key] = $result;
                    }
                }
                $request->bind($bind);
            }
    
            if (!empty($option['response'])) {
                Hook::add('response_send', $option['response']);
            }
    
            // 解析额外参数
            self::parseUrlParams(empty($paths) ? '' : implode('|', $paths), $matches);
            // 记录匹配的路由信息
            $request->routeInfo(['rule' => $rule, 'route' => $route, 'option' => $option, 'var' => $matches]);
    
            // 检测路由after行为
            if (!empty($option['after_behavior'])) {
                if ($option['after_behavior'] instanceof \Closure) {
                    $result = call_user_func_array($option['after_behavior'], []);
                } else {
                    foreach ((array) $option['after_behavior'] as $behavior) {
                        $result = Hook::exec($behavior, '');
                        if (!is_null($result)) {
                            break;
                        }
                    }
                }
                // 路由规则重定向
                if ($result instanceof Response) {
                    return ['type' => 'response', 'response' => $result];
                } elseif (is_array($result)) {
                    return $result;
                }
            }
    
            if ($route instanceof \Closure) {
                // 执行闭包
                $result = ['type' => 'function', 'function' => $route];
            } elseif (0 === strpos($route, '/') || strpos($route, '://')) {
                // 路由到重定向地址
                $result = ['type' => 'redirect', 'url' => $route, 'status' => isset($option['status']) ? $option['status'] : 301];
            } elseif (false !== strpos($route, '\\')) {
                // 路由到方法
                list($path, $var) = self::parseUrlPath($route);
                $route            = str_replace('/', '@', implode('/', $path));
                $method           = strpos($route, '@') ? explode('@', $route) : $route;
                $result           = ['type' => 'method', 'method' => $method, 'var' => $var];
            } elseif (0 === strpos($route, '@')) {
                // 路由到控制器
                $route             = substr($route, 1);
                list($route, $var) = self::parseUrlPath($route);
                $result            = ['type' => 'controller', 'controller' => implode('/', $route), 'var' => $var];
                $request->action(array_pop($route));
                $request->controller($route ? array_pop($route) : Config::get('default_controller'));
                $request->module($route ? array_pop($route) : Config::get('default_module'));
                App::$modulePath = APP_PATH . (Config::get('app_multi_module') ? $request->module() . DS : '');
            } else {
                // 路由到模块/控制器/操作
                $result = self::parseModule($route, isset($option['convert']) ? $option['convert'] : false);
            }
            // 开启请求缓存
            if ($request->isGet() && isset($option['cache'])) {
                $cache = $option['cache'];
                if (is_array($cache)) {
                    list($key, $expire, $tag) = array_pad($cache, 3, null);
                } else {
                    $key    = str_replace('|', '/', $pathinfo);
                    $expire = $cache;
                    $tag    = null;
                }
                $request->cache($key, $expire, $tag);
            }
            return $result;
        }
    

    11.解析URL地址为 模块/控制器/操作

      /**
         * 解析URL地址为 模块/控制器/操作
         * @access private
         * @param string $url     URL地址
         * @param bool   $convert 是否自动转换URL地址
         * @return array
         */
        private static function parseModule($url, $convert = false)
        {
            list($path, $var) = self::parseUrlPath($url);
            $action           = array_pop($path);
            $controller       = !empty($path) ? array_pop($path) : null;
            $module           = Config::get('app_multi_module') && !empty($path) ? array_pop($path) : null;
            $method           = Request::instance()->method();
            if (Config::get('use_action_prefix') && !empty(self::$methodPrefix[$method])) {
                // 操作方法前缀支持
                $action = 0 !== strpos($action, self::$methodPrefix[$method]) ? self::$methodPrefix[$method] . $action : $action;
            }
            // 设置当前请求的路由变量
            Request::instance()->route($var);
            // 路由到模块/控制器/操作
            return ['type' => 'module', 'module' => [$module, $controller, $action], 'convert' => $convert];
        }
    

    12.解析URL地址中的参数Request对象

      /**
         * 解析URL地址中的参数Request对象
         * @access private
         * @param string $url 路由规则
         * @param array  $var 变量
         * @return void
         */
        private static function parseUrlParams($url, &$var = [])
        {
            if ($url) {
                if (Config::get('url_param_type')) {
                    $var += explode('|', $url);
                } else {
                    preg_replace_callback('/(\w+)\|([^\|]+)/', function ($match) use (&$var) {
                        $var[$match[1]] = strip_tags($match[2]);
                    }, $url);
                }
            }
            // 设置当前请求的参数
            Request::instance()->route($var);
        }
    

    13.分析路由规则中的变量

        // 分析路由规则中的变量
        private static function parseVar($rule)
        {
            // 提取路由规则中的变量
            $var = [];
            foreach (explode('/', $rule) as $val) {
                $optional = false;
                if (false !== strpos($val, '<') && preg_match_all('/<(\w+(\??))>/', $val, $matches)) {
                    foreach ($matches[1] as $name) {
                        if (strpos($name, '?')) {
                            $name     = substr($name, 0, -1);
                            $optional = true;
                        } else {
                            $optional = false;
                        }
                        $var[$name] = $optional ? 2 : 1;
                    }
                }
    
                if (0 === strpos($val, '[:')) {
                    // 可选参数
                    $optional = true;
                    $val      = substr($val, 1, -1);
                }
                if (0 === strpos($val, ':')) {
                    // URL变量
                    $name       = substr($val, 1);
                    $var[$name] = $optional ? 2 : 1;
                }
            }
            return $var;
        }
    
    

    14.用反射执行类的方法 支持参数绑定

       /**
         * 调用反射执行类的方法 支持参数绑定
         * @access public
         * @param string|array $method 方法
         * @param array        $vars   变量
         * @return mixed
         */
        public static function invokeMethod($method, $vars = [])
        {
            if (is_array($method)) {
                $class   = is_object($method[0]) ? $method[0] : self::invokeClass($method[0]);
                $reflect = new \ReflectionMethod($class, $method[1]);
            } else {
                // 静态方法
                $reflect = new \ReflectionMethod($method);
            }
    
            $args = self::bindParams($reflect, $vars);
    
            self::$debug && Log::record('[ RUN ] ' . $reflect->class . '->' . $reflect->name . '[ ' . $reflect->getFileName() . ' ]', 'info');
    
            return $reflect->invokeArgs(isset($class) ? $class : null, $args);
        
        }
    

    15.调用反射执行类的实例化 支持依赖注入

     /**
         * 调用反射执行类的实例化 支持依赖注入
         * @access public
         * @param string $class 类名
         * @param array  $vars  变量
         * @return mixed
         */
        public static function invokeClass($class, $vars = [])
        {
            $reflect     = new \ReflectionClass($class);
            $constructor = $reflect->getConstructor();
            $args        = $constructor ? self::bindParams($constructor, $vars) : [];
    
            return $reflect->newInstanceArgs($args);
        }
    
    
    展开全文
  • tp5.1源码分析

    2019-12-25 15:57:47
    new self() 和 new static() 的区别 parse_ini_file — 解析一个配置文件 parse_ini_file ( string $filename [, bool $process_sections = false [, int $scanner_mode = INI_SCANNER_NORMAL ]] ) : array parse_...
    • 函数/类 及作用
    1. spl_autoload_register — 注册给定的函数作为 __autoload 的实现

    spl_autoload_register ([ callable $autoload_function [, bool $throw = true [, bool $prepend = false ]]] ) : bool
    参数:
    autoload_function
    欲注册的自动装载函数。如果没有提供任何参数,则自动注册 autoload 的默认实现函数spl_autoload()。
    throw
    此参数设置了 autoload_function 无法成功注册时, spl_autoload_register()是否抛出异常。
    prepend
    如果是 true,spl_autoload_register() 会添加函数到队列之首,而不是队列尾部。
    返回值:
    成功时返回 TRUE, 或者在失败时返回 FALSE。

    1. property_exists — 检查对象或类是否具有该属性

    property_exists ( mixed $class , string $property ) : bool
    本函数检查给出的 property 是否存在于指定的类中(以及是否能在当前范围内访问)。
    参数:
    class 字符串形式的类名或要检查的类的一个对象
    property 属性的名字
    返回值:
    如果该属性存在则返回 TRUE,如果不存在则返回 FALSE,出错返回 NULL。

    1. get_declared_classes — 返回由已定义类的名字所组成的数组

    get_declared_classes ( void ) : array

    1. class_alias — 为一个类创建别名

    class_alias ( string $original , string $alias [, bool $autoload = TRUE ] ) : bool
    基于用户定义的类 original 创建别名 alias。 这个别名类和原有的类完全相同。
    参数:
    original 原有的类。
    alias 类的别名。
    autoload 如果原始类没有加载,是否使用自动加载(autoload)。
    返回值:
    成功时返回 TRUE, 或者在失败时返回 FALSE。

    1. strpos — 查找字符串首次出现的位置

    strpos ( string $haystack , mixed $needle [, int $offset = 0 ] ) : int
    返回 needle 在 haystack 中首次出现的数字位置。
    参数:
    haystack 在该字符串中进行查找。
    needle 如果 needle 不是一个字符串,那么它将被转换为整型并被视为字符的顺序值。
    offset 如果提供了此参数,搜索会从字符串该字符数的起始位置开始统计。 如果是负数,搜索会从字符串结尾指定字符数开始。
    返回值:
    返回 needle 存在于 haystack 字符串起始的位置(独立于 offset)。同时注意字符串位置是从0开始,而不是从1开始的。
    如果没找到 needle,将返回 FALSE。
    Warning
    此函数可能返回布尔值 FALSE,但也可能返回等同于 FALSE 的非布尔值。请阅读 布尔类型章节以获取更多信息。应使用 === 运算符来测试此函数的返回值。

    1. scandir — 列出指定路径中的文件和目录

    scandir ( string $directory [, int $sorting_order = SCANDIR_SORT_ASCENDING [, resource $context ]] ) : array
    返回一个 array,包含有 directory 中的文件和目录。
    参数:
    directory 要被浏览的目录
    sorting_order 默认的排序顺序是按字母升序排列。如果使用了可选参数 sorting_order(设为 1),则排序顺序是按字母降序排列。
    context context 参数的说明见手册中的 Streams API 一章。
    返回值:
    成功则返回包含有文件名的 array,如果失败则返回 FALSE。如果 directory 不是个目录,则返回布尔值 FALSE 并生成一条 E_WARNING 级的错误。

    1. pathinfo — 返回文件路径的信息

    pathinfo ( string $path [, int $options = PATHINFO_DIRNAME | PATHINFO_BASENAME | PATHINFO_EXTENSION | PATHINFO_FILENAME ] ) : mixed
    pathinfo() 返回一个关联数组包含有 path 的信息。返回关联数组还是字符串取决于 options。
    参数:
    path 要解析的路径。
    options 如果指定了,将会返回指定元素;它们包括:PATHINFO_DIRNAME,PATHINFO_BASENAME 和 PATHINFO_EXTENSION 或 PATHINFO_FILENAME。如果没有指定options 默认是返回全部的单元。
    返回值
    如果没有传入 options ,将会返回包括以下单元的数组 array:dirname,basename 和 extension(如果有),以 及filename。

    1. new self() 和 new static() 的区别

    2. parse_ini_file — 解析一个配置文件

    parse_ini_file ( string $filename [, bool $process_sections = false [, int $scanner_mode = INI_SCANNER_NORMAL ]] ) : array
    parse_ini_file() 载入一个由 filename 指定的 ini 文件,并将其中的设置作为一个联合数组返回。
    ini 文件的结构和 php.ini 的相似。
    参数:
    filename 要解析的 ini 文件的文件名。
    process_sections 如果将最后的 process_sections 参数设为 TRUE,将得到一个多维数组,包括了配置文件中每一节的名称和设置。process_sections 的默认值是 FALSE。
    scanner_mode Can either be INI_SCANNER_NORMAL (default) or INI_SCANNER_RAW. If INI_SCANNER_RAW is supplied, then option values will not be parsed.
    返回值:
    成功时以关联数组 array 返回设置,失败时返回 FALSE。

    1. DIRECTORY_SEPARATOR

    DIRECTORY_SEPARATOR是一个返回跟操作系统相关的路径分隔符的php内置命令,在windows上返回,而在linux或者类unix上返回/,就是这么个区别,通常在定义包含文件路径或者上传保存目录的时候会用到。

    1. is_file — 判断给定文件名是否为一个正常的文件

    is_file ( string $filename ) : bool
    判断给定文件名是否为一个正常的文件。
    参数:
    filename 文件的路径。
    返回值:
    如果文件存在且为正常的文件则返回 TRUE,否则返回 FALSE。

    1. ReflectionClass 类报告了一个类的有关信息。

    2. ReflectionMethod 类报告了一个方法的有关信息。

    3. _callStatic()方法。从PHP5.3开始出现此方法,当创建一个静态方法以调用该类中不存在的一个方法时使用此函数。与__call()方法相同,接受方法名和数组作为参数。

    4. call_user_func_array — 调用回调函数,并把一个数组参数作为回调函数的参数

    call_user_func_array ( callable $callback , array $param_arr ) : mixed
    把第一个参数作为回调函数(callback)调用,把参数数组作(param_arr)为回调函数的的参数传入。
    参数:
    callback 被调用的回调函数。
    param_arr 要被传入回调函数的数组,这个数组得是索引数组。
    返回值:
    返回回调函数的结果。如果出错的话就返回FALSE

    1. array_change_key_case — 将数组中的所有键名修改为全大写或小写

    array_change_key_case ( array $array [, int $case = CASE_LOWER ] ) : array
    array_change_key_case() 将 array 数组中的所有键名改为全小写或大写。本函数不改变数字索引。
    参数:
    array 需要操作的数组。
    case 可以在这里用两个常量,CASE_UPPER 或 CASE_LOWER(默认值)。
    返回值:
    返回一个键全是小写或者全是大写的数组;如果输入值(array)不是一个数组,那么返回FALSE
    错误/异常:
    如果输入值(array)不是一个数组,就会抛出一个错误警告(E_WARNING)。

    1. debug_backtrace — 产生一条回溯跟踪(backtrace)

    debug_backtrace ([ int $options = DEBUG_BACKTRACE_PROVIDE_OBJECT [, int $limit = 0 ]] ) : array
    debug_backtrace() 产生一条 PHP 的回溯跟踪(backtrace)。
    参数:
    options
    截至 5.3.6,这个参数是以下选项的位掩码:
    debug_backtrace() 选项
    DEBUG_BACKTRACE_PROVIDE_OBJECT 是否填充 “object” 的索引。
    DEBUG_BACKTRACE_IGNORE_ARGS 是否忽略 “args” 的索引,包括所有的 function/method 的参数,能够节省内存开销。在 5.3.6 之前,仅仅能使用的值是 TRUE 或者 FALSE,分别等于是否设置 DEBUG_BACKTRACE_PROVIDE_OBJECT 选项。
    limit 截至 5.4.0,这个参数能够用于限制返回堆栈帧的数量。 默认为 (limit=0) ,返回所有的堆栈帧。
    返回值:
    返回一个包含众多关联数组的 array。 以为为有可能返回的元素:

    1. preg_quote()
    2. ucwords() 把每个单词的首字符转换为大写
    3. preg_split()
    4. strncasecmp()
    5. list()
    • 属性设置

    thinkphp5.1属性分析图

    • 配置加载流程图
      配置加载流程图
    • config 类库分析
      在这里插入图片描述
    • 容器
      容器的介绍
    • 路由脑图
      路由脑图
    展开全文
  • tp3.2源码解析

    2019-04-02 17:21:35
    如果有人读这篇文章并跟着做的话,希望你能使用支持函数跳转的编辑器,还要善用var_dump和exit,对着源码去调试着看。跟着入口文件读,执行到哪里你看到哪里,对于那些不能一眼看出来的配置,则要记录下来,可能一个...

    链接:https://www.cnblogs.com/wyycc/p/8922430.html 

    如果有人读这篇文章并跟着做的话,希望你能使用支持函数跳转的编辑器,还要善用var_dump和exit,对着源码去调试着看。跟着入口文件读,执行到哪里你看到哪里,对于那些不能一眼看出来的配置,则要记录下来,可能一个比较简单的功能会写出很长的代码,这个时候难免会看到后面忘了前面。

      那么进入正题,从index.php文件可以看到入口文件只定义了几项常量作为配置,紧接着就引入了require './ThinkPHP/ThinkPHP.php';

      

     1 // 检测PHP环境
     2 if(version_compare(PHP_VERSION,'5.3.0','<'))  die('require PHP > 5.3.0 !');
     3 
     4 // 开启调试模式 建议开发阶段开启 部署阶段注释或者设为false
     5 define('APP_DEBUG',True);
     6 
     7 // 定义应用目录
     8 define('APP_PATH','./Application/');
     9 
    10 // 引入ThinkPHP入口文件
    11 require './ThinkPHP/ThinkPHP.php';

     

      在ThinkpPHP文件依然比较简单,tp定义了一些常量配置项(defined函数的写法让之前在index入口文件里定义的配置项不会被重置)记录了运行时间和内存使用信息,进行了php版本的判断,以及cli命令行模式的判断。并在文件末尾再次引入了Think核心类,并进行了初始化。

      require CORE_PATH.'Think'.EXT;路径为ThinkPHP\Library\Think\Think.class.php

      这个think类就比较长了,一开始就定义了$_map  ,   $_instance两个数组,其中$_map作为映射数组使用,think类在会把核心模块的路劲存在这个数组里。$_instance则存储了系统运行时所实例化的对象

    1     // 类映射
    2     private static $_map      = array();
    3 
    4     // 实例化对象
    5     private static $_instance = array();

      刚刚的入口文件执行了start方法来运行系统。这个start方法则一开始就通过spl_autoload_register方法注册了自动加载函数。(php本身有一些魔术方法,在执行某些方法,或变量时,如果它找不到这个方法或变量,就会执行相应的魔术方法,tp便是用自己的自动引入方法替换了该方法,达到不需要引入,直接new对象,系统便会自动引入该类文件的目的)

     

     1 static public function start() {
     2       // 注册AUTOLOAD方法
     3       spl_autoload_register('Think\Think::autoload');      
     4       // 设定错误和异常处理
     5       register_shutdown_function('Think\Think::fatalError');
     6       set_error_handler('Think\Think::appError');
     7       set_exception_handler('Think\Think::appException');
     8 
     9       // 初始化文件存储方式
    10       Storage::connect(STORAGE_TYPE);
    11       ················

      我们往下翻看,autoload方法传入了一个$class类名,然后便在类的$_map里检测是否存在该类的映射,如果有,则表明是系统核心模块直接通过存储的地址include引入;反之则会判断$class是否为带有命名空间的路径字符串,然后通过strstr函数分割字符串获得命名空间前缀,判断是否为tp系统定义的命名空间,然后便通过之前定义的常量来确定文件路径。同时判断是否为win环境,进行大小写的匹配,然后include引入;

     autoload

      通过APP_USE_NAMESPACE判断如果配置项里未使用命名空间,那么通过APP_AUTOLOAD_LAYER配置项循环判断为控制器或模型,调用公共方法判断路径并require。若引入失败则再按照配置文件中的APP_AUTOLOAD_PATH路径寻找引入;

      注册了autoload方法后,tp系统又注册了错误及异常处理方法,接管了报错时的信息提示功能(这些方法与autoload差不多,有兴趣的朋友自己研究,在这里就不一一赘述了)。

      然后它通过Storage::connect(STORAGE_TYPE);类初始化了自己的文件存储类。ThinkPHP\Library\Think\Storage.class.php该类简单的定义了一个操作句柄,一个初始化方法,将各种不同方式的操作对象赋予本类,(通过传入不同参数,可以确定为SAE环境不同类型的存储操作,默认为file文件操作类)

     1 namespace Think;
     2 // 分布式文件存储类
     3 class Storage {
     4 
     5     /**
     6      * 操作句柄
     7      * @var string
     8      * @access protected
     9      */
    10     static protected $handler    ;
    11 
    12     /**
    13      * 连接分布式文件系统
    14      * @access public
    15      * @param string $type 文件类型
    16      * @param array $options  配置数组
    17      * @return void
    18      */
    19     static public function connect($type='File',$options=array()) {
    20         $class  =   'Think\\Storage\\Driver\\'.ucwords($type);
    21         self::$handler = new $class($options);
    22     }
    23 
    24     static public function __callstatic($method,$args){
    25         //调用缓存驱动的方法
    26         if(method_exists(self::$handler, $method)){
    27            return call_user_func_array(array(self::$handler,$method), $args);
    28         }
    29     }
    30 }

      file文件位于ThinkPHP\Library\Think\Storage\Driver\File.class.php(tp通过不同的driver驱动层,来适应不同环境,不同类型的动态操作)这个文件也是写的相当简单,类方法里定义了写入,删除,加载,读取,等基本操作。

      回到start方法,Tp通过APP_DEBUG配置来判断是否有runtime缓存文件,通过Storage::has方法来读取,或unlink方法删除该缓存文件。

    1 $runtimefile  = RUNTIME_PATH.APP_MODE.'~runtime.php';
    2       if(!APP_DEBUG && Storage::has($runtimefile)){
    3           Storage::load($runtimefile);
    4       }else{
    5           if(Storage::has($runtimefile))
    6               Storage::unlink($runtimefile);

      然后它读取了应用模式:

     1 $content =  '';
     2           // 读取应用模式
     3           $mode   =   include is_file(CONF_PATH.'core.php')?CONF_PATH.'core.php':MODE_PATH.APP_MODE.'.php';
     4           // 加载核心文件
     5           foreach ($mode['core'] as $file){
     6               if(is_file($file)) {
     7                 include $file;
     8                 if(!APP_DEBUG) $content   .= compile($file);
     9               }
    10           }
    1 is_file(CONF_PATH.'core.php')?  //判断是否有隐含应用模式文件
    2 CONF_PATH.'core.php':MODE_PATH.APP_MODE.'.php';
    3 //yes,读取Application/Common/Conf/core.php ; 否,读取/ThinkPHP/Mode/common.php

      

      大家打开ThinkPHP/Common/functions.php文件,这个文件里通过数组定义了配置文件和行文扩展,其中core里面存储了tp的核心文件。(大家在查看源码的时候,可以多用var_dump和exit这两个函数来查看一些变量常量的值)这里放一下这个配置文件的路径

      

     1 //载入配置列表
     2 Array
     3 (
     4     [config] => Array
     5         (
     6             [0] => D:\wamp\www\tp\ThinkPHP/Conf/convention.php
     7             [1] => ./Application/Common/Conf/config.php
     8         )
     9 //增加为映射
    10     [alias] => Array
    11         (
    12             [Think\Log] => D:\wamp\www\tp\ThinkPHP\Library/Think/Log.class.php
    13             [Think\Log\Driver\File] => D:\wamp\www\tp\ThinkPHP\Library/Think/Log/Driver/File.class.php
    14             [Think\Exception] => D:\wamp\www\tp\ThinkPHP\Library/Think/Exception.class.php
    15             [Think\Model] => D:\wamp\www\tp\ThinkPHP\Library/Think/Model.class.php
    16             [Think\Db] => D:\wamp\www\tp\ThinkPHP\Library/Think/Db.class.php
    17             [Think\Template] => D:\wamp\www\tp\ThinkPHP\Library/Think/Template.class.php
    18             [Think\Cache] => D:\wamp\www\tp\ThinkPHP\Library/Think/Cache.class.php
    19             [Think\Cache\Driver\File] => D:\wamp\www\tp\ThinkPHP\Library/Think/Cache/Driver/File.class.php
    20             [Think\Storage] => D:\wamp\www\tp\ThinkPHP\Library/Think/Storage.class.php
    21         )
    22 
    23 //加载核心类
    24     [core] => Array
    25         (
    26             [0] => D:\wamp\www\tp\ThinkPHP/Common/functions.php
    27             [1] => ./Application/Common/Common/function.php
    28             [2] => D:\wamp\www\tp\ThinkPHP\Library/Think/Hook.class.php
    29             [3] => D:\wamp\www\tp\ThinkPHP\Library/Think/App.class.php
    30             [4] => D:\wamp\www\tp\ThinkPHP\Library/Think/Dispatcher.class.php
    31             [5] => D:\wamp\www\tp\ThinkPHP\Library/Think/Route.class.php
    32             [6] => D:\wamp\www\tp\ThinkPHP\Library/Think/Controller.class.php
    33             [7] => D:\wamp\www\tp\ThinkPHP\Library/Think/View.class.php
    34             [8] => D:\wamp\www\tp\ThinkPHP\Library/Behavior/BuildLiteBehavior.class.php
    35             [9] => D:\wamp\www\tp\ThinkPHP\Library/Behavior/ParseTemplateBehavior.class.php
    36             [10] => D:\wamp\www\tp\ThinkPHP\Library/Behavior/ContentReplaceBehavior.class.php
    37         )
    38 //加载到Think/Hook->tags里
    39     [tags] => Array
    40         (
    41             [app_init] => Array
    42                 (
    43                     [0] => Behavior\BuildLiteBehavior
    44                 )
    45 
    46             [app_begin] => Array
    47                 (
    48                     [0] => Behavior\ReadHtmlCacheBehavior
    49                 )
    50 
    51             [app_end] => Array
    52                 (
    53                     [0] => Behavior\ShowPageTraceBehavior
    54                 )
    55 
    56             [view_parse] => Array
    57                 (
    58                     [0] => Behavior\ParseTemplateBehavior
    59                 )
    60 
    61             [template_filter] => Array
    62                 (
    63                     [0] => Behavior\ContentReplaceBehavior
    64                 )
    65 
    66             [view_filter] => Array
    67                 (
    68                     [0] => Behavior\WriteHtmlCacheBehavior
    69                 )
    70 
    71         )
    72 
    73 )

       在include核心文件后,通过C方法加载了应用模式的配置。(C方法位于ThinkPHP\Common\functions.php,tp的单字母函数都是在这里定义的,也是比较简单,通过静态变量来存储配置)

     

      function C($name=null, $value=null,$default=null)这里解释下auto自动变量会随着函数被调用和退出而存在和消失,而static类局部变量不会,它不管其所在的函数是否被调用,都将一直存在;不过,尽管该变量还继续存在,但不能使用它。倘若再次调用定义它的函数时,它又可继续使用,而且保存了前次被调用后留下的值。

      加载了配置项,又通过map定义了模式的别名。

      然后加载了应用行为定义,这里的行为比较关键,解释一下这个概念。

      行为就是钩子函数,有些了解钩子函数的同学可能已经知道这是干嘛的了,这里解释一下,钩子函数就是系统在运行过程中,挂在某一段代码中的方法,在代码执行到钩子方法那里的时候就会执行这个钩子上所绑的函数了,不了解的同学可以理解为方法间的调用,比如我有一个a方法,一个b方法,a方法里显示的写了b();这样来调用b方法,这样虽然能起到调用的目的,但是一旦程序需要改动,要把调用b方法换成调用c方法则需要改动所有写了b();的地方,十分繁琐,还可能出错,于是,如果我们在需要调用b方法的地方,不显示的调用b方法,而是读取一个配置变量,或配置文件,调用配置里定义的方法,那现在这样写就把a方法和b方法的耦合给解开了,以后要改变b方法为c方法d方法的时候我都可以只改动配置文件,是不是很方便呢?

     

    1 Function a(){
    2     ````
    3     B();
    4 `````
    5 }
    6 
    7 Function b(){
    8     Echo ‘我是钩子函数b’;
    9 }

      如果你已经理解了钩子函数,那么ThinkPHP\Library\Think\Hook.class.php这就是tp里的钩子类,用来挂载行为(tp中把钩子函数叫做行为)。打开这个文件来看一下(这里我就不整篇贴代码了):

    1. 一开始定义了一个$tags变量,用来存储需要执行的方法。
    2. Add方法添插件行为(就是钩子函数),import方法批量导入,
    3. get获取插件数组,
    4. exec方法执行插件,
    5. listen方法则是线判断了是否为debug模式,如果是debug模式,则通过G方法记录了插件的执行,再调用exec方法,最后通过trace记录了日志。

       看完了hook类,再回到think类来,加载应用行为就很好理解了,通过Hook::import将tags.php里配置的钩子数组导入了hook类里(tp里定义钩子函数通过config里新建tags.php,不清楚的朋友自行翻阅手册)。Tp的行为在上面放出的tags配置数组里可以看到。

      加载完行为,又加载了语言包、引入debug文件、读取应用状态的配置文件、创建基本目录结构、记录加载文件时间。

     View Code

      最后app:run()又调用了另一个类,整个配置完成,开始运行了。ThinkPHP\Library\Think\App.class.php

      Run方法一开始就通过刚刚说的Hook::listen方法调用了一个行为,然后执行了init方法来初始化系统。

     1     static public function run() {
     2         // 应用初始化标签
     3         Hook::listen('app_init');
     4         App::init();
     5         // 应用开始标签
     6         Hook::listen('app_begin');
     7         // Session初始化
     8         if(!IS_CLI){
     9             session(C('SESSION_OPTIONS'));
    10         }
    11         // 记录应用初始化时间
    12         G('initTime');
    13         App::exec();
    14         // 应用结束标签
    15         Hook::listen('app_end');
    16         return ;
    17     }

      Init方法也是一开始就加载了公共配置,配置了日志路径,以及定义了不少常量,都是与服务器接到的请求有关。

     1     static public function init() {
     2         // 加载动态应用公共文件和配置
     3         load_ext_file(COMMON_PATH);
     4 
     5         // 日志目录转换为绝对路径 默认情况下存储到公共模块下面
     6         C('LOG_PATH',   realpath(LOG_PATH).'/Common/');
     7 
     8         // 定义当前请求的系统常量
     9         define('NOW_TIME',      $_SERVER['REQUEST_TIME']);
    10         define('REQUEST_METHOD',$_SERVER['REQUEST_METHOD']);
    11         define('IS_GET',        REQUEST_METHOD =='GET' ? true : false);
    12         define('IS_POST',       REQUEST_METHOD =='POST' ? true : false);
    13         define('IS_PUT',        REQUEST_METHOD =='PUT' ? true : false);
    14         define('IS_DELETE',     REQUEST_METHOD =='DELETE' ? true : false);
    15 
    16         // URL调度
    17         Dispatcher::dispatch();
    18         ``````````````

      Dispatcher::dispatch(); 这个方法就是用来解析路由并进行控制器调用的。文件位于ThinkPHP\Library\Think\ Dispatcher.class.php

      这个方法一上来就获取了不少配置,接着对get到的参数进行判断是否为兼容模式或cli命令行模式。又对子域名部署进行了判断,分析了pathinfo信息。

     1     static public function dispatch() {
     2         $varPath        =   C('VAR_PATHINFO');
     3         $varAddon       =   C('VAR_ADDON');
     4         $varModule      =   C('VAR_MODULE');
     5         $varController  =   C('VAR_CONTROLLER');
     6         $varAction      =   C('VAR_ACTION');
     7         $urlCase        =   C('URL_CASE_INSENSITIVE');
     8         if(isset($_GET[$varPath])) { // 判断URL里面是否有兼容模式参数
     9             $_SERVER['PATH_INFO'] = $_GET[$varPath];
    10             unset($_GET[$varPath]);
    11         }elseif(IS_CLI){ // CLI模式下 index.php module/controller/action/params/...
    12             $_SERVER['PATH_INFO'] = isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : '';
    13         }
    14         // 开启子域名部署
    15         if(C('APP_SUB_DOMAIN_DEPLOY')) {
    16             ````````
    17         }    
    18         // 分析PATHINFO信息
    19         if(!isset($_SERVER['PATH_INFO'])) {
    20              ````````
    21         }

      然后开始进入正题了,先通过URL_PATHINFO_DEPR获取url的分隔符,然后是从$_SERVER里获取PATH_INFO用作路由截取,存入__INFO__常量,这里有个判断未绑定模,未开启路由配置,以及路由检测的判断,Route::check()

      这个动态路由处理部分我也不是很清楚,不过最后它返回了false,满足了外面的if条件。接下来就通过配置变量对url进行了拆解,建议大家把这些变量配置都打印出来,看一眼便知道了。

     

     1         $depr = C('URL_PATHINFO_DEPR');
     2         define('MODULE_PATHINFO_DEPR',  $depr);
     3         
     4         if(empty($_SERVER['PATH_INFO'])) {
     5             $_SERVER['PATH_INFO'] = '';
     6             define('__INFO__','');
     7             define('__EXT__','');
     8         }else{
     9             define('__INFO__',trim($_SERVER['PATH_INFO'],'/'));
    10             // URL后缀
    11             define('__EXT__', strtolower(pathinfo($_SERVER['PATH_INFO'],PATHINFO_EXTENSION)));
    12             $_SERVER['PATH_INFO'] = __INFO__;     
    13             if(!defined('BIND_MODULE') && (!C('URL_ROUTER_ON') || !Route::check())){
    14                 if (__INFO__ && C('MULTI_MODULE')){ // 获取模块名
    15                     $paths      =   explode($depr,__INFO__,2);
    16                     $allowList  =   C('MODULE_ALLOW_LIST'); // 允许的模块列表
    17                     $module     =   preg_replace('/\.' . __EXT__ . '$/i', '',$paths[0]);
    18                     if( empty($allowList) || (is_array($allowList) && in_array_case($module, $allowList))){
    19                         $_GET[$varModule]       =   $module;
    20                         $_SERVER['PATH_INFO']   =   isset($paths[1])?$paths[1]:'';
    21                     }
    22                 }
    23             }             
    24         }
    25         `````````````

      真正获取到路由的是define('__SELF__',strip_tags($_SERVER[C('URL_REQUEST_URI')]));这一句,通过系统$_SERVER全局变量中的REQUEST_URI项获取到完整的url路由。

      再剩下的都是根据配置常量来对url进行处理,加载相应模块的配置了,费点时间依次打印记录就能很清晰的看出来只是对字符串进行拆分、过滤、拼接。

      至于路由传参则是由这一段正则进行匹配的。preg_replace_callback('/(\w+)\/([^\/]+)/', function($match) use(&$var){$var[$match[1]]=strip_tags($match[2]);}, implode('/',$paths));

       获取到mca及自定义参数后,url解析完成,调回init方法,开启url钩子行为

      回到APP类,init方法初始化完成,开启app_begin钩子行为,记录session配置,调用exec执行方法。

     

      Exec方法也是一开始就又对控制器名进行了过滤,然后正常情况会直接跳到$module  =  controller(CONTROLLER_NAME,CONTROLLER_PATH);这里创建控制器对象。我们跳到controller函数这里,也是简单的字符拼接后就直接new出了对象,得到对象以后又是跳转到了invokeAction方法这里。

     1     static public function exec() {
     2     
     3         if(!preg_match('/^[A-Za-z](\/|\w)*$/',CONTROLLER_NAME)){ // 安全检测
     4             $module  =  false;
     5         }elseif(C('ACTION_BIND_CLASS')){
     6             // 操作绑定到类:模块\Controller\控制器\操作
     7             $layer  =   C('DEFAULT_C_LAYER');
     8             if(is_dir(MODULE_PATH.$layer.'/'.CONTROLLER_NAME)){
     9                 $namespace  =   MODULE_NAME.'\\'.$layer.'\\'.CONTROLLER_NAME.'\\';
    10             }else{
    11                 // 空控制器
    12                 $namespace  =   MODULE_NAME.'\\'.$layer.'\\_empty\\';                    
    13             }
    14             $actionName     =   strtolower(ACTION_NAME);
    15             if(class_exists($namespace.$actionName)){
    16                 $class   =  $namespace.$actionName;
    17             }elseif(class_exists($namespace.'_empty')){
    18                 // 空操作
    19                 $class   =  $namespace.'_empty';
    20             }else{
    21                 E(L('_ERROR_ACTION_').':'.ACTION_NAME);
    22             }
    23             $module  =  new $class;
    24             // 操作绑定到类后 固定执行run入口
    25             $action  =  'run';
    26         }else{
    27             //创建控制器实例
    28             $module  =  controller(CONTROLLER_NAME,CONTROLLER_PATH);                
    29         }
    30 
    31         if(!$module) {
    32             if('4e5e5d7364f443e28fbf0d3ae744a59a' == CONTROLLER_NAME) {
    33                 header("Content-type:image/png");
    34                 exit(base64_decode(App::logo()));
    35             }
    36 
    37             // 是否定义Empty控制器
    38             $module = A('Empty');
    39             if(!$module){
    40                 E(L('_CONTROLLER_NOT_EXIST_').':'.CONTROLLER_NAME);
    41             }
    42         }
    43 
    44         // 获取当前操作名 支持动态路由
    45         if(!isset($action)){
    46             $action    =   ACTION_NAME.C('ACTION_SUFFIX');  
    47         }
    48         try{
    49             self::invokeAction($module,$action);
    50         } catch (\ReflectionException $e) { 
    51             // 方法调用发生异常后 引导到__call方法处理
    52             $method = new \ReflectionMethod($module,'__call');
    53             $method->invokeArgs($module,array($action,''));
    54         }
    55         return ;
    56     }

      invokeAction方法在一开始进行安全过滤之后,就利用了ReflectionMethod这个反射类,对我们刚刚的控制器对象进行了检测,有无_before前置操作啊,通过反射类获取方法参数列表,并将获取到的参数赋值并执行,判断后置方法执行之类的。到这里,我们的控制器方法就已经开始执行了。

     

     1     public static function invokeAction($module,$action){
     2         if(!preg_match('/^[A-Za-z](\w)*$/',$action)){
     3             // 非法操作
     4             throw new \ReflectionException();
     5         }
     6         //执行当前操作
     7         $method =   new \ReflectionMethod($module, $action);
     8         if($method->isPublic() && !$method->isStatic()) {
     9             $class  =   new \ReflectionClass($module);
    10             // 前置操作
    11             if($class->hasMethod('_before_'.$action)) {
    12                 $before =   $class->getMethod('_before_'.$action);
    13                 if($before->isPublic()) {
    14                     $before->invoke($module);
    15                 }
    16             }
    17             // URL参数绑定检测
    18             if($method->getNumberOfParameters()>0 && C('URL_PARAMS_BIND')){
    19                 switch($_SERVER['REQUEST_METHOD']) {
    20                     case 'POST':
    21                         $vars    =  array_merge($_GET,$_POST);
    22                         break;
    23                     case 'PUT':
    24                         parse_str(file_get_contents('php://input'), $vars);
    25                         break;
    26                     default:
    27                         $vars  =  $_GET;
    28                 }
    29                 $params =  $method->getParameters();
    30                 $paramsBindType     =   C('URL_PARAMS_BIND_TYPE');
    31                 foreach ($params as $param){
    32                     $name = $param->getName();
    33                     if( 1 == $paramsBindType && !empty($vars) ){
    34                         $args[] =   array_shift($vars);
    35                     }elseif( 0 == $paramsBindType && isset($vars[$name])){
    36                         $args[] =   $vars[$name];
    37                     }elseif($param->isDefaultValueAvailable()){
    38                         $args[] =   $param->getDefaultValue();
    39                     }else{
    40                         E(L('_PARAM_ERROR_').':'.$name);
    41                     }   
    42                 }
    43                 // 开启绑定参数过滤机制
    44                 if(C('URL_PARAMS_SAFE')){
    45                     $filters     =   C('URL_PARAMS_FILTER')?:C('DEFAULT_FILTER');
    46                     if($filters) {
    47                         $filters    =   explode(',',$filters);
    48                         foreach($filters as $filter){
    49                             $args   =   array_map_recursive($filter,$args); // 参数过滤
    50                         }
    51                     }                        
    52                 }
    53                 array_walk_recursive($args,'think_filter');
    54                 $method->invokeArgs($module,$args);
    55             }else{
    56                 $method->invoke($module);
    57             }
    58             // 后置操作
    59             if($class->hasMethod('_after_'.$action)) {
    60                 $after =   $class->getMethod('_after_'.$action);
    61                 if($after->isPublic()) {
    62                     $after->invoke($module);
    63                 }
    64             }
    65         }else{
    66             // 操作方法不是Public 抛出异常
    67             throw new \ReflectionException();
    68         }
    69     }

      好的,到这里。tp框架最复杂最麻烦的前置准备工作已经全部完成了。

    展开全文
  • thinkphp5源码解析
  • 文章目录分析上图展望 分析 public\index.php 【执行应用并响应】 Container::get(‘app’)->run()->send(); 接下来我们要分析前半句执行应用部分 Container::get(‘app’)->run() ......

    文章目录

    分析

    • public\index.php
    • 【执行应用并响应】

    Container::get(‘app’)->run()->send();

    • 接下来我们要分析前半句执行应用部分
      Container::get(‘app’)->run()

    上图

    在这里插入图片描述

    展望

    • 接下来的时间里我们来玩玩$this->initialize() ;这也是个大活, 期待吧!!!
    展开全文
  • 我还是把源码附上 public static function register($autoload = '') { //【1】 注册系统自动加载 //【2】 Composer自动加载支持 // 【3】注册命名空间定义 // 【4】加载类库映射文件 if (is_file($rootPath...
  • 把loader主要的功能提取出来,从属性...附上源码 <?php namespace think; use think\exception\ClassNotFoundException; class Loader { /** * 类名映射信息 * @var array */ protected static $classM..
  • 第三方素材解析源码分享给大家伙儿,仅限于研究哈。我也是朋友分享给我的。喜欢的自己拿走研究,tp内核。
  • TP6源码解析,ThinkPHP6模板编译流程详解 前言:刚开始写博客。如果觉得本篇文章对您有所帮助。点个赞再走也不迟 模板编译流程,大概是: 先获取到View类实例(依赖注入也好,通过助手函数也好) 使用View编译方法...
  • tp5+excel上传下载

    2019-01-25 10:22:32
    基于tp5下做的excel上传和下载设置了几个字段,根据个人需要设置字段
  • TP3.2 源码解析

    2019-09-24 13:48:56
    请求到达入口文件 干了几件事情: ... 2、定义应用的目录,通常是我们写业务逻辑的地方 3、引入入口文件 ...2、定义url模式,版本信息、定义框架需要的常量、和应用相关目录 ...以上大概就是tp3的运行流程
  • l2tp内核源码

    2012-10-29 23:04:02
    l2tp内核源码
  • 安卓体验包与安卓源码下载地址: 加密方面,登陆的密码都是使用md5加盐加密,sign跟access_user_token使用AES加密. 要求 依赖 说明 PHP >=5.4 且 <=7.1.9 Thinkphp 5.0.10 MySQL >=5.5 nginx 用于网址代理解析 集成...
  • tp5 Cache解析

    2019-07-04 14:38:04
    tp5的Cache类目录:/thinkphp/library/think/Cache.php (适配器)各类型驱动:/thinkphp/library/think/cache/Driver.php Cache配置:对应模块application/config.php 一览Cache类方法: init()初始化 使用:...
  • 封装微信支付所需的所有方法,复制粘贴,修改配置公众号信息即可使用 完整代码+代码解析文档,亲测可用
  • 如果有人读这篇文章并跟着做的话,希望你能使用支持函数跳转的编辑器,还要善用var_dump和exit,对着源码去调试着看。跟着入口文件读,执行到哪里你看到哪里,对于那些不能一眼看出来的配置,则要记录下来,可能一个...
  • 这个类里面有很这样的使用例如$this->hook->import(); $this->config->get(); $this->request->init();等等 你知道这个对象怎么来的吗?不急,亮仔一一道来: ...app类会继承容器Container(这是关键...
  • ThinkPHP5.0 源码解析

    2018-11-06 11:50:57
    写框架要从了解框架开始,本场 Chat 将手把手带你了解 ThinkPHP,让你从一个只会用...面向读者:PHP 初级,中级开发者,对 ThinkPHP 源码不了解的开发者。 通过本场 Chat 您将学到: ThinkPHP 框架运行流程。 Th...
  • 第2章 【TP5灵魂】自动加载Loader 深度分析(重点章节,请认真听,建议多次回听) 1.关于PHP中spl_autoload_register()函数用法详解 https://www.php.cn/php-weizijiaocheng-400053.html
  • Thinkphp6源码解析之分析 路由篇-请求流程 0x00 前言: 第一次写这么长的博客,所以可能排版啊,分析啊,什么的可能会比较乱。但是我大致的流程已经觉得是说的够清楚了。几乎是每行源码上都有注释。关于请求流程...
  • 1.解析器配置,指定路径 首先我们在nginx 或者 apache的配置文件中指定了该项目的路径是到public。 eg:nginx: server { listen 80; #listen [::]:80 default_server ipv6only=on; server_name www.t5.cn; ...
  • TP5框架目录解析

    2019-08-17 11:38:54
    |-index(这里的文件夹tp5叫做模块-----一般是前台模块,也可以根据需要需求修改成其他(例如:home),需要修改配置文件,修改默认模块、控制器、操作) 【注】:TP5默认只有一个index文件(模块)和一个控制层...
  • socket 源码解析之创建

    2019-02-25 17:23:54
    数据结构 /** * struct socket - general BSD socket * @state: socket state (%SS_CONNECTED, etc) * @flags: socket flags (%SOCK_ASYNC_NOSPACE, etc) * @ops: protocol specific socket operations ...
  • MTK平台TP驱动框架解析

    千次阅读 2019-10-20 19:33:08
    一,TP驱动代码的组成 MTK平台TP驱动主要包括两处文件: 1,kernel-3.18\drivers\input\touchscreen\mediatek\mtk_tpd.c 平台设备驱动,主要为了兼容多个不同的TP驱动IC,它会去遍历每一个编译并添加进 tpd_...
  • 下面也就其框架对一定的解析。 运行机制及流程 1.入口文件index.php 独立模式 define('APP_PATH', __DIR__ . '/../application/'); define('APP_DEBUG', true);require __DIR__ . '/../thinkphp/start.php'; ...
  • tp5的数据库操作全部通过Db类完成,比较符合国人的习惯,比如简单的Db::query()、Db::execute(),还有复杂的链式操作Db::table('user')-&gt;where('id=1')-&gt;select(),下面就通过源码来了解其工作流程 ...
  • Kafka 源码解析

    2019-08-19 11:42:53
    请求,加入 group 并获取其 assign 的 tp list; 检测心跳线程运行是否正常 (需要定时向 GroupCoordinator 发送心跳线程,长时间未发送的话 group就会认为该实例已经挂了)   Consumer.poll() ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 4,887
精华内容 1,954
关键字:

tp5源码解析