f::getRouteExpress($key); } if (!isset($item['rule'])) { continue; } $rule = $item['rule']; $route = $item['route']; $vars = $item['var']; $option = $item['option']; $pattern = $item['pattern']; // 检查参数有效性 if (!self::checkOption($option, $request)) { continue; } if (isset($option['ext'])) { // 路由ext参数 优先于系统配置的URL伪静态后缀参数 $url = preg_replace('/\.' . $request->ext() . '$/i', '', $url); } if (is_array($rule)) { // 分组路由 $pos = strpos(str_replace('<', ':', $key), ':'); if (false !== $pos) { $str = substr($key, 0, $pos); } else { $str = $key; } if (is_string($str) && $str && 0 !== stripos(str_replace('|', '/', $url), $str)) { continue; } self::setOption($option); $result = self::checkRoute($request, $rule, $url, $depr, $key, $option); if (false !== $result) { return $result; } } elseif ($route) { if ('__miss__' == $rule || '__auto__' == $rule) { // 指定特殊路由 $var = trim($rule, '__'); ${$var} = $item; continue; } if ($group) { $rule = $group . ($rule ? '/' . ltrim($rule, '/') : ''); } self::setOption($option); if (isset($options['bind_model']) && isset($option['bind_model'])) { $option['bind_model'] = array_merge($options['bind_model'], $option['bind_model']); } $result = self::checkRule($rule, $route, $url, $pattern, $option, $depr); if (false !== $result) { return $result; } } } if (isset($auto)) { // 自动解析URL地址 return self::parseUrl($auto['route'] . '/' . $url, $depr); } elseif (isset($miss)) { // 未匹配所有路由的路由规则处理 return self::parseRule('', $miss['route'], $url, $miss['option']); } return false; } /** * 检测路由别名 * @access private * @param Request $request * @param string $url URL地址 * @param string $depr URL分隔符 * @return mixed */ private static function checkRouteAlias($request, $url, $depr) { $array = explode('|', $url); $alias = array_shift($array); $item = self::$rules['alias'][$alias]; if (is_array($item)) { list($rule, $option) = $item; $action = $array[0]; if (isset($option['allow']) && !in_array($action, explode(',', $option['allow']))) { // 允许操作 return false; } elseif (isset($option['except']) && in_array($action, explode(',', $option['except']))) { // 排除操作 return false; } if (isset($option['method'][$action])) { $option['method'] = $option['method'][$action]; } } else { $rule = $item; } $bind = implode('|', $array); // 参数有效性检查 if (isset($option) && !self::checkOption($option, $request)) { // 路由不匹配 return false; } elseif (0 === strpos($rule, '\\')) { // 路由到类 return self::bindToClass($bind, substr($rule, 1), $depr); } elseif (0 === strpos($rule, '@')) { // 路由到控制器类 return self::bindToController($bind, substr($rule, 1), $depr); } else { // 路由到模块/控制器 return self::bindToModule($bind, $rule, $depr); } } /** * 检测URL绑定 * @access private * @param string $url URL地址 * @param array $rules 路由规则 * @param string $depr URL分隔符 * @return mixed */ private static function checkUrlBind(&$url, &$rules, $depr = '/') { if (!empty(self::$bind)) { $type = self::$bind['type']; $bind = self::$bind[$type]; // 记录绑定信息 App::$debug && Log::record('[ BIND ] ' . var_export($bind, true), 'info'); // 如果有URL绑定 则进行绑定检测 switch ($type) { case 'class': // 绑定到类 return self::bindToClass($url, $bind, $depr); case 'controller': // 绑定到控制器类 return self::bindToController($url, $bind, $depr); case 'namespace': // 绑定到命名空间 return self::bindToNamespace($url, $bind, $depr); } } return false; } /** * 绑定到类 * @access public * @param string $url URL地址 * @param string $class 类名(带命名空间) * @param string $depr URL分隔符 * @return array */ public static function bindToClass($url, $class, $depr = '/') { $url = str_replace($depr, '|', $url); $array = explode('|', $url, 2); $action = !empty($array[0]) ? $array[0] : Config::get('default_action'); if (!empty($array[1])) { self::parseUrlParams($array[1]); } return ['type' => 'method', 'method' => [$class, $action], 'var' => []]; } /** * 绑定到命名空间 * @access public * @param string $url URL地址 * @param string $namespace 命名空间 * @param string $depr URL分隔符 * @return array */ public static function bindToNamespace($url, $namespace, $depr = '/') { $url = str_replace($depr, '|', $url); $array = explode('|', $url, 3); $class = !empty($array[0]) ? $array[0] : Config::get('default_controller'); $method = !empty($array[1]) ? $array[1] : Config::get('default_action'); if (!empty($array[2])) { self::parseUrlParams($array[2]); } return ['type' => 'method', 'method' => [$namespace . '\\' . Loader::parseName($class, 1), $method], 'var' => []]; } /** * 绑定到控制器类 * @access public * @param string $url URL地址 * @param string $controller 控制器名 (支持带模块名 index/user ) * @param string $depr URL分隔符 * @return array */ public static function bindToController($url, $controller, $depr = '/') { $url = str_replace($depr, '|', $url); $array = explode('|', $url, 2); $action = !empty($array[0]) ? $array[0] : Config::get('default_action'); if (!empty($array[1])) { self::parseUrlParams($array[1]); } return ['type' => 'controller', 'controller' => $controller . '/' . $action, 'var' => []]; } /** * 绑定到模块/控制器 * @access public * @param string $url URL地址 * @param string $controller 控制器类名(带命名空间) * @param string $depr URL分隔符 * @return array */ public static function bindToModule($url, $controller, $depr = '/') { $url = str_replace($depr, '|', $url); $array = explode('|', $url, 2); $action = !empty($array[0]) ? $array[0] : Config::get('default_action'); if (!empty($array[1])) { self::parseUrlParams($array[1]); } return ['type' => 'module', 'module' => $controller . '/' . $action]; } /** * 路由参数有效性检查 * @access private * @param array $option 路由参数 * @param Request $request Request对象 * @return bool */ private static function checkOption($option, $request) { if ((isset($option['method']) && is_string($option['method']) && false === stripos($option['method'], $request->method())) || (isset($option['ajax']) && $option['ajax'] && !$request->isAjax()) // Ajax检测 || (isset($option['ajax']) && !$option['ajax'] && $request->isAjax()) // 非Ajax检测 || (isset($option['pjax']) && $option['pjax'] && !$request->isPjax()) // Pjax检测 || (isset($option['pjax']) && !$option['pjax'] && $request->isPjax()) // 非Pjax检测 || (isset($option['ext']) && false === stripos('|' . $option['ext'] . '|', '|' . $request->ext() . '|')) // 伪静态后缀检测 || (isset($option['deny_ext']) && false !== stripos('|' . $option['deny_ext'] . '|', '|' . $request->ext() . '|')) || (isset($option['domain']) && !in_array($option['domain'], [$_SERVER['HTTP_HOST'], self::$subDomain])) // 域名检测 || (isset($option['https']) && $option['https'] && !$request->isSsl()) // https检测 || (isset($option['https']) && !$option['https'] && $request->isSsl()) // https检测 || (!empty($option['before_behavior']) && false === Hook::exec($option['before_behavior'])) // 行为检测 || (!empty($option['callback']) && is_callable($option['callback']) && false === call_user_func($option['callback'])) // 自定义检测 ) { return false; } return true; } /** * 检测路由规则 * @access private * @param string $rule 路由规则 * @param string $route 路由地址 * @param string $url URL地址 * @param array $pattern 变量规则 * @param array $option 路由参数 * @param string $depr URL分隔符(全局) * @return array|false */ private static function checkRule($rule, $route, $url, $pattern, $option, $depr) { // 检查完整规则定义 if (isset($pattern['__url__']) && !preg_match(0 === strpos($pattern['__url__'], '/') ? $pattern['__url__'] : '/^' . $pattern['__url__'] . '/', str_replace('|', $depr, $url))) { return false; } // 检查路由的参数分隔符 if (isset($option['param_depr'])) { $url = str_replace(['|', $option['param_depr']], [$depr, '|'], $url); } $len1 = substr_count($url, '|'); $len2 = substr_count($rule, '/'); // 多余参数是否合并 $merge = !empty($option['merge_extra_vars']); if ($merge && $len1 > $len2) { $url = str_replace('|', $depr, $url); $url = implode('|', explode($depr, $url, $len2 + 1)); } if ($len1 >= $len2 || strpos($rule, '[')) { if (!empty($option['complete_match'])) { // 完整匹配 if (!$merge && $len1 != $len2 && (false === strpos($rule, '[') || $len1 > $len2 || $len1 < $len2 - substr_count($rule, '['))) { return false; } } $pattern = array_merge(self::$rules['pattern'], $pattern); if (false !== $match = self::match($url, $rule, $pattern)) { // 匹配到路由规则 return self::parseRule($rule, $route, $url, $option, $match); } } return false; } /** * 解析模块的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]; } /** * 解析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]; } /** * 检测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中定义了多个变量 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; } /** * 解析规则路由 * @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; } /** * 解析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]; } /** * 解析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); } // 分析路由规则中的变量 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; } /** * 获取路由解析缓存的key * @param Request $request * @return string */ private static function getCheckCacheKey(Request $request) { static $key; if (empty($key)) { if ($callback = Config::get('route_check_cache_key')) { $key = call_user_func($callback, $request); } else { $key = "{$request->host(true)}|{$request->method()}|{$request->path()}"; } } return $key; } }