// +---------------------------------------------------------------------- namespace think; use think\App; use think\Config; use think\Hook; use think\Log; use think\Request; use think\Response; class Route { // 路由规则 private static $rules = [ 'GET' => [], 'POST' => [], 'PUT' => [], 'DELETE' => [], 'PATCH' => [], 'HEAD' => [], 'OPTIONS' => [], '*' => [], 'map' => [], 'alias' => [], 'domain' => [], 'pattern' => [], ]; // REST路由操作方法定义 private static $rest = [ 'index' => ['GET', '', 'index'], 'create' => ['GET', '/create', 'create'], 'edit' => ['GET', '/:id/edit', 'edit'], 'read' => ['GET', '/:id', 'read'], 'save' => ['POST', '', 'save'], 'update' => ['PUT', '/:id', 'update'], 'delete' => ['DELETE', '/:id', 'delete'], ]; // 不同请求类型的方法前缀 private static $methodPrefix = [ 'GET' => 'get', 'POST' => 'post', 'PUT' => 'put', 'DELETE' => 'delete', ]; // 子域名 private static $subDomain = ''; // 域名绑定 private static $bind = []; // 当前分组 private static $group = ''; // 当前参数 private static $option = []; /** * 注册变量规则 * @access public * @param string|array $name 变量名 * @param string $rule 变量规则 * @return void */ public static function pattern($name = null, $rule = '') { if (is_array($name)) { self::$rules['pattern'] = array_merge(self::$rules['pattern'], $name); } else { self::$rules['pattern'][$name] = $rule; } } /** * 注册子域名部署规则 * @access public * @param string|array $domain 子域名 * @param mixed $rule 路由规则 * @return void */ public static function domain($domain = null, $rule = '') { if (is_array($domain)) { self::$rules['domain'] = array_merge(self::$rules['domain'], $domain); } else { self::$rules['domain'][$domain] = $rule; } } /** * 设置路由绑定 * @access public * @param mixed $bind 绑定信息 * @param string $type 绑定类型 默认为module * @return mixed */ public static function bind($bind, $type = 'module') { self::$bind = ['type' => $type, $type => $bind]; } /** * 读取路由绑定 * @access public * @param string $type 绑定类型 * @return mixed */ public static function getBind($type) { return isset(self::$bind[$type]) ? self::$bind[$type] : null; } /** * 导入配置文件的路由规则 * @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__']); } $type = strtoupper($type); foreach ($rule as $key => $val) { if (is_numeric($key)) { $key = array_shift($val); } if (empty($val)) { continue; } if (0 === strpos($key, '[')) { $key = substr($key, 1, -1); self::group($key, $val); } elseif (is_array($val)) { self::setRule($key, $val[0], $type, $val[1], isset($val[2]) ? $val[2] : []); } else { self::setRule($key, $val, $type); } } } /** * 注册路由规则 * @access public * @param string $rule 路由规则 * @param string $route 路由地址 * @param string $type 请求类型 * @param array $option 路由参数 * @param array $pattern 变量规则 * @return void */ public static function rule($rule, $route = '', $type = '*', $option = [], $pattern = []) { $group = self::$group; $option = array_merge(self::$option, $option); $type = strtoupper($type); if (strpos($type, '|')) { $option['method'] = $type; $type = '*'; } if (is_array($rule)) { foreach ($rule as $key => $val) { if (is_numeric($key)) { $key = array_shift($val); } if (is_array($val)) { $route = $val[0]; $option1 = array_merge($option, $val[1]); $pattern1 = array_merge($pattern, isset($val[2]) ? $val[2] : []); } else { $route = $val; } self::setRule($key, $route, $type, isset($option1) ? $option1 : $option, isset($pattern1) ? $pattern1 : $pattern, $group); } } else { self::setRule($rule, $route, $type, $option, $pattern, $group); } } /** * 设置路由规则 * @access public * @param string $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 ('$' == substr($rule, -1, 1)) { // 是否完整匹配 $option['complete_match'] = true; $rule = substr($rule, 0, -1); } if ('/' != $rule) { $rule = trim($rule, '/'); } $vars = self::parseVar($rule); if ($group) { self::$rules[$type][$group]['rule'][] = ['rule' => $rule, 'route' => $route, 'var' => $vars, 'option' => $option, 'pattern' => $pattern]; } else { if ('*' != $type && isset(self::$rules['*'][$rule])) { unset(self::$rules['*'][$rule]); } 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) { self::$rules[$method][$rule] = true; } } } } /** * 设置当前的路由分组 * @access public * @param array $option 路由参数 * @return void */ public static function setGroup($name) { if (self::$group) { self::$group = self::$group . '/' . ltrim($name, '/'); } else { self::$group = $name; } } /** * 设置当前的路由参数 * @access public * @param array $option 路由参数 * @return void */ public static function setOption($option) { self::$option = $option; } /** * 注册路由分组 * @access public * @param string|array $name 分组名称或者参数 * @param array|\Closure $routes 路由地址 * @param array $option 路由参数 * @param string $type 请求类型 * @param array $pattern 变量规则 * @return void */ public static function group($name, $routes, $option = [], $type = '*', $pattern = []) { if (is_array($name)) { $option = $name; $name = isset($option['name']) ? $option['name'] : ''; } $type = strtoupper($type); if (!empty($name)) { // 分组 if ($routes instanceof \Closure) { $curentGroup = self::$group; self::setGroup($name); call_user_func_array($routes, []); self::$group = $curentGroup; self::$rules[$type][$name]['route'] = ''; self::$rules[$type][$name]['var'] = self::parseVar($name); self::$rules[$type][$name]['option'] = $option; self::$rules[$type][$name]['pattern'] = $pattern; } else { foreach ($routes as $key => $val) { if (is_numeric($key)) { $key = array_shift($val); } if (is_array($val)) { $route = $val[0]; $option1 = array_merge($option, $val[1]); $pattern1 = array_merge($pattern, isset($val[2]) ? $val[2] : []); } else { $route = $val; } $vars = self::parseVar($key); $item[] = ['rule' => $key, 'route' => $route, 'var' => $vars, 'option' => isset($option1) ? $option1 : $option, 'pattern' => isset($pattern1) ? $pattern1 : $pattern]; } self::$rules[$type][$name] = ['rule' => $item, 'route' => '', 'var' => [], 'option' => $option, 'pattern' => $pattern]; } if ('*' == $type) { foreach (['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'] as $method) { if (!isset(self::$rules[$method][$name])) { self::$rules[$method][$name] = true; } else { self::$rules[$method][$name] = array_merge(self::$rules['*'][$name], self::$rules[$method][$name]); } } } } else { if ($routes instanceof \Closure) { // 闭包注册 self::setOption($option); call_user_func_array($routes, []); self::setOption([]); } else { // 批量注册路由 self::rule($routes, '', $type, $option, $pattern); } } } /** * 注册路由 * @access public * @param string $rule 路由规则 * @param string $route 路由地址 * @param array $option 路由参数 * @param array $pattern 变量规则 * @return void */ public static function any($rule, $route = '', $option = [], $pattern = []) { self::rule($rule, $route, '*', $option, $pattern); } /** * 注册GET路由 * @access public * @param string $rule 路由规则 * @param string $route 路由地址 * @param array $option 路由参数 * @param array $pattern 变量规则 * @return void */ public static function get($rule, $route = '', $option = [], $pattern = []) { self::rule($rule, $route, 'GET', $option, $pattern); } /** * 注册POST路由 * @access public * @param string $rule 路由规则 * @param string $route 路由地址 * @param array $option 路由参数 * @param array $pattern 变量规则 * @return void */ public static function post($rule, $route = '', $option = [], $pattern = []) { self::rule($rule, $route, 'POST', $option, $pattern); } /** * 注册PUT路由 * @access public * @param string $rule 路由规则 * @param string $route 路由地址 * @param array $option 路由参数 * @param array $pattern 变量规则 * @return void */ public static function put($rule, $route = '', $option = [], $pattern = []) { self::rule($rule, $route, 'PUT', $option, $pattern); } /** * 注册DELETE路由 * @access public * @param string $rule 路由规则 * @param string $route 路由地址 * @param array $option 路由参数 * @param array $pattern 变量规则 * @return void */ public static function delete($rule, $route = '', $option = [], $pattern = []) { self::rule($rule, $route, 'DELETE', $option, $pattern); } /** * 注册PATCH路由 * @access public * @param string $rule 路由规则 * @param string $route 路由地址 * @param array $option 路由参数 * @param array $pattern 变量规则 * @return void */ public static function patch($rule, $route = '', $option = [], $pattern = []) { self::rule($rule, $route, 'PATCH', $option, $pattern); } /** * 注册资源路由 * @access public * @param string $rule 路由规则 * @param string $route 路由地址 * @param array $option 路由参数 * @param array $pattern 变量规则 * @return void */ public static function resource($rule, $route = '', $option = [], $pattern = []) { if (is_array($rule)) { foreach ($rule as $key => $val) { if (is_array($val)) { list($val, $option, $pattern) = array_pad($val, 3, []); } self::resource($key, $val, $option, $pattern); } } else { if (strpos($rule, '.')) { // 注册嵌套资源路由 $array = explode('.', $rule); $last = array_pop($array); $item = []; foreach ($array as $val) { $item[] = $val . '/:' . (isset($option['var'][$val]) ? $option['var'][$val] : $val . '_id'); } $rule = implode('/', $item) . '/' . $last; } // 注册资源路由 foreach (self::$rest as $key => $val) { if ((isset($option['only']) && !in_array($key, $option['only'])) || (isset($option['except']) && in_array($key, $option['except']))) { continue; } if (isset($last) && strpos($val[1], ':id') && isset($option['var'][$last])) { $val[1] = str_replace(':id', ':' . $option['var'][$last], $val[1]); } elseif (strpos($val[1], ':id') && isset($option['var'][$rule])) { $val[1] = str_replace(':id', ':' . $option['var'][$rule], $val[1]); } $item = ltrim($rule . $val[1], '/'); self::rule($item ? $item . '$' : '', $route . '/' . $val[2], $val[0], $option, $pattern); } } } /** * 注册控制器路由 操作方法对应不同的请求后缀 * @access public * @param string $rule 路由规则 * @param string $route 路由地址 * @param array $option 路由参数 * @param array $pattern 变量规则 * @return void */ public static function controller($rule, $route = '', $option = [], $pattern = []) { foreach (self::$methodPrefix as $type => $val) { self::$type($rule . '/:action', $route . '/' . $val . ':action', $option, $pattern); } } /** * 注册别名路由 * @access public * @param string|array $rule 路由别名 * @param string $route 路由地址 * @param array $option 路由参数 * @return void */ public static function alias($rule = null, $route = '', $option = []) { if (is_array($rule)) { self::$rules['alias'] = array_merge(self::$rules['alias'], $rule); } else { self::$rules['alias'][$rule] = $option ? [$route, $option] : $route; } } /** * 设置不同请求类型下面的方法前缀 * @access public * @param string $method 请求类型 * @param string $prefix 类型前缀 * @return void */ public static function setMethodPrefix($method, $prefix = '') { if (is_array($method)) { self::$methodPrefix = array_merge(self::$methodPrefix, array_change_key_case($method, CASE_UPPER)); } else { self::$methodPrefix[strtoupper($method)] = $prefix; } } /** * rest方法定义和修改 * @access public * @param string $name 方法名称 * @param array $resourece 资源 * @return void */ public static function rest($name, $resource = []) { if (is_array($name)) { self::$rest = array_merge(self::$rest, $name); } else { self::$rest[$name] = $resource; } } /** * 注册未匹配路由规则后的处理 * @access public * @param string $route 路由地址 * @param string $method 请求类型 * @param array $option 路由参数 * @return void */ public static function miss($route, $method = '*', $option = []) { self::rule('__miss__', $route, $method, $option, []); } /** * 获取或者批量设置路由定义 * @access public * @param mixed $rules 请求类型或者路由定义数组 * @return array */ public static function rules($rules = '') { if (is_array($rules)) { self::$rules = $rules; } elseif ($rules) { return self::$rules[$rules]; } else { $rules = self::$rules; unset($rules['pattern'], $rules['alias'], $rules['domain']); return $rules; } } /** * 检测子域名部署 * @access public * @param Request $request Request请求对象 * @return void */ public static function checkDomain($request) { // 域名规则 $rules = self::$rules['domain']; // 开启子域名部署 支持二级和三级域名 if (!empty($rules)) { $host = $request->host(); if (isset($rules[$host])) { // 完整域名或者IP配置 $rule = $rules[$host]; } else { $rootDomain = Config::get('url_domain_root'); if ($rootDomain) { // 配置域名根 例如 thinkphp.cn 163.com.cn 如果是国家级域名 com.cn net.cn 之类的域名需要配置 $domain = explode('.', rtrim(stristr($host, $rootDomain, true), '.')); } else { $domain = explode('.', $host, -2); } // 子域名配置 if (!empty($domain)) { // 当前子域名 $subDomain = implode('.', $domain); self::$subDomain = $subDomain; $domain2 = array_pop($domain); if ($domain) { // 存在三级域名 $domain3 = array_pop($domain); } if ($subDomain && isset($rules[$subDomain])) { // 子域名配置 $rule = $rules[$subDomain]; } elseif (isset($rules['*.' . $domain2]) && !empty($domain3)) { // 泛三级域名 $rule = $rules['*.' . $domain2]; $panDomain = $domain3; } elseif (isset($rules['*']) && !empty($domain2)) { // 泛二级域名 if ('www' != $domain2) { $rule = $rules['*']; $panDomain = $domain2; } } } } if (!empty($rule)) { // 子域名部署规则 if ($rule instanceof \Closure) { // 执行闭包 $reflect = new \ReflectionFunction($rule); self::$bind = $reflect->invokeArgs([]); return; } if (strpos($rule, '?')) { // 传入其它参数 $array = parse_url($rule); $result = $array['path']; parse_str($array['query'], $params); if (isset($panDomain)) { $pos = array_search('*', $params); if (false !== $pos) { // 泛域名作为参数 $params[$pos] = $panDomain; } } $_GET = array_merge($_GET, $params); } else { $result = $rule; } if (0 === strpos($result, '\\')) { // 绑定到命名空间 例如 \app\index\behavior self::$bind = ['type' => 'namespace', 'namespace' => $result]; } elseif (0 === strpos($result, '@')) { // 绑定到类 例如 \app\index\controller\User self::$bind = ['type' => 'class', 'class' => substr($result, 1)]; } elseif (0 === strpos($result, '[')) { // 绑定到分组 例如 [user] self::$bind = ['type' => 'group', 'group' => substr($result, 1, -1)]; } else { // 绑定到模块/控制器 例如 index/user self::$bind = ['type' => 'module', 'module' => $result]; } } } } /** * 检测URL路由 * @access public * @param Request $request Request请求对象 * @param string $url URL地址 * @param string $depr URL分隔符 * @param bool $checkDomain 是否检测域名规则 * @return false|array */ public static function check($request, $url, $depr = '/', $checkDomain = false) { // 分隔符替换 确保路由定义使用统一的分隔符 if ('/' != $depr) { $url = str_replace($depr, '/', $url); } if (strpos($url, '/') && isset(self::$rules['alias'][strstr($url, '/', true)])) { // 检测路由别名 $result = self::checkRouteAlias($request, $url, $depr); if (false !== $result) { return $result; } } // 检测域名部署 if ($checkDomain) { self::checkDomain($request); } // 获取当前请求类型的路由规则 $rules = self::$rules[$request->method()]; // 检测URL绑定 $return = self::checkUrlBind($url, $rules, $depr); if (false !== $return) { return $return; } if (isset($rules[$url])) { // 静态路由规则检测 $rule = $rules[$url]; if (true === $rule) { $rule = self::$rules['*'][$url]; } return self::parseRule($url, $rule['route'], $url, $rule['option']); } // 路由规则检测 if (!empty($rules)) { return self::checkRoute($request, $rules, $url); } return false; } /** * 检测路由规则 * @access private * @param Request $request * @param array $rules 路由规则 * @param string $url URL地址 * @param string $group 路由分组名 * @return mixed */ private static function checkRoute($request, $rules, $url, $group = '') { foreach ($rules as $key => $item) { if (true === $item) { $item = self::$rules['*'][$key]; } $rule = $item['rule']; $route = $item['route']; $vars = $item['var']; $option = $item['option']; $pattern = $item['pattern']; // 检查参数有效性 if (!self::checkOption($option, $url, $request)) { continue; } if (is_array($rule)) { // 分组路由 if (($pos = strpos($key, ':')) || ($pos = strpos($key, '<'))) { $str = substr($key, 0, $pos); } else { $str = $key; } if (0 !== strpos($url, $str)) { continue; } $result = self::checkRoute($request, $rule, $url, $key); if (false !== $result) { return $result; } } else { if ('__miss__' == $rule) { // 指定MISS路由 $miss = $item; continue; } if ($group) { $rule = $group . ($rule ? '/' . ltrim($rule, '/') : ''); } $result = self::checkRule($rule, $route, $url, $pattern, $option); if (false !== $result) { return $result; } } } if (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, 2); $item = self::$rules['alias'][$array[0]]; if (is_array($item)) { list($rule, $option) = $item; } else { $rule = $item; } // 参数有效性检查 if (isset($option) && !self::checkOption($option, $url, $request)) { // 路由不匹配 return false; } elseif (0 === strpos($rule, '\\')) { // 路由到类 return self::bindToClass($array[1], substr($rule, 1), $depr); } elseif (0 === strpos($url, '@')) { // 路由到控制器类 return self::bindToController($array[1], substr($rule, 1), $depr); } else { // 路由到模块/控制器 return self::bindToModule($array[1], $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'])) { // 记录绑定信息 App::$debug && Log::record('[ BIND ] ' . var_export(self::$bind, true), 'info'); // 如果有URL绑定 则进行绑定检测 switch (self::$bind['type']) { case 'class': // 绑定到类 return self::bindToClass($url, self::$bind['class'], $depr); case 'namespace': // 绑定到命名空间 return self::bindToNamespace($url, self::$bind['namespace'], $depr); case 'module': // 如果有模块/控制器绑定 针对路由到 模块/控制器 有效 $url = self::$bind['module'] . '/' . $url; break; case 'group': // 绑定到路由分组 $key = self::$bind['group']; if (array_key_exists($key, $rules)) { $rules = [$key => $rules[self::$bind['group']]]; } } } return false; } /** * 绑定到类 * @access public * @param string $url URL地址 * @param string $class 类名(带命名空间) * @param string $depr URL分隔符 * @return array */ public static function bindToClass($url, $class, $depr = '/') { $array = explode($depr, $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], 'params' => []]; } /** * 绑定到命名空间 * @access public * @param string $url URL地址 * @param string $namespace 命名空间 * @param string $depr URL分隔符 * @return array */ public static function bindToNamespace($url, $namespace, $depr = '/') { $array = explode($depr, $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 . '\\' . $class, $method], 'params' => []]; } /** * 绑定到控制器类 * @access public * @param string $url URL地址 * @param string $controller 控制器名 (支持带模块名 index/user ) * @param string $depr URL分隔符 * @return array */ public static function bindToController($url, $controller, $depr = '/') { $array = explode($depr, $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, 'params' => []]; } /** * 绑定到模块/控制器 * @access public * @param string $url URL地址 * @param string $class 控制器类名(带命名空间) * @param string $depr URL分隔符 * @return array */ public static function bindToModule($url, $controller, $depr = '/') { $array = explode($depr, $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 string $url URL地址 * @param Request $request Request对象 * @return bool */ private static function checkOption($option, $url, $request) { // 请求类型检测 if ((isset($option['method']) && false === stripos($option['method'], $request->method())) || (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])) // 域名检测 || (!empty($option['https']) && !$request->isSsl()) // https检测 || (!empty($option['before_behavior']) && false === Hook::exec($option['before_behavior'], '', $url)) // 行为检测 || (!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 路由参数 * @return array|false */ private static function checkRule($rule, $route, $url, $pattern, $option) { // 检查完整规则定义 if (isset($pattern['__url__']) && !preg_match('/^' . $pattern['__url__'] . '/', $url)) { return false; } // 检测是否设置了参数分隔符 if ($depr = Config::get('url_params_depr')) { $url = str_replace($depr, '/', $url); $rule = str_replace($depr, '/', $rule); } $len1 = substr_count($url, '/'); $len2 = substr_count($rule, '/'); // 多余参数是否合并 $merge = !empty($option['merge_extra_vars']) ? true : false; 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, $merge)) { // 匹配到路由规则 return self::parseRule($rule, $route, $url, $option, $match, $merge); } } 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'])) { // 如果有模块/控制器绑定 $url = self::$bind['module'] . '/' . $url; } list($path, $var) = self::parseUrlPath($url, $depr); $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 : '') . 'controller'; $suffix = App::$suffix || Config::get('controller_suffix') ? ucfirst(Config::get('url_controller_layer')) : ''; $item = []; foreach ($path as $val) { $item[] = array_shift($path); if (is_file($dir . DS . $val . $suffix . EXT)) { break; } else { $dir .= DS . $val; } } $controller = implode('.', $item); } 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]; } return ['type' => 'module', 'module' => $route]; } /** * 解析URL的pathinfo参数和变量 * @access private * @param string $url URL地址 * @param string $depr URL分隔符 * @return array */ private static function parseUrlPath($url, $depr = '/') { // 分隔符替换 确保路由定义使用统一的分隔符 if ('/' != $depr) { $url = str_replace($depr, '/', $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); } elseif (false !== strpos($url, '=')) { // 参数1=值1&参数2=值2... parse_str($url, $var); } else { $path = [$url]; } return [$path, $var]; } /** * 检测URL和规则路由是否匹配 * @access private * @param string $url URL地址 * @param string $rule 路由规则 * @param array $pattern 变量规则 * @param bool $merge 合并额外变量 * @return array|false */ private static function match($url, $rule, $pattern, $merge) { $m2 = explode('/', $rule); $m1 = $merge ? explode('/', $url, count($m2)) : explode('/', $url); $var = []; foreach ($m2 as $key => $val) { // val中定义了多个变量 if (false !== strpos($val, '<') && preg_match_all('/<(\w+(\??))>/', $val, $matches)) { $value = []; foreach ($matches[1] as $name) { if (strpos($name, '?')) { $name = substr($name, 0, -1); $replace[] = '(' . (isset($pattern[$name]) ? $pattern[$name] : '') . '?)'; } else { $replace[] = '(' . (isset($pattern[$name]) ? $pattern[$name] : '') . ')'; } $value[] = $name; } $val = str_replace($matches[0], $replace, $val); if (preg_match('/^' . $val . '$/', $m1[$key], $match)) { array_shift($match); $match = array_slice($match, 0, count($value)); $var = array_merge($var, array_combine($value, $match)); 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]) && !preg_match('/^' . $pattern[$name] . '$/', $m1[$key])) { // 检查变量规则 return false; } $var[$name] = isset($m1[$key]) ? $m1[$key] : ''; } elseif (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 $merge 合并额外变量 * @return array */ private static function parseRule($rule, $route, $pathinfo, $option = [], $matches = [], $merge = false) { // 检测是否定义路由 if (!empty($option['after_behavior'])) { if ($option['after_behavior'] instanceof \Closure) { $result = call_user_func_array($option['after_behavior'], [$route]); } else { foreach ((array) $option['after_behavior'] as $behavior) { $result = Hook::exec($behavior, '', $route); if (!is_null($result)) { break; } } } // 路由规则重定向 if ($result instanceof Response) { return ['type' => 'response', 'response' => $result, 'params' => $matches]; } elseif (is_array($result)) { return $result; } } // 解析路由规则 if ($rule) { $rule = explode('/', $rule); // 获取URL地址中的参数 $paths = $merge ? explode('/', $pathinfo, count($rule)) : 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); } // 获取路由地址规则 $url = $route; // 替换路由地址中的变量 if (is_string($url) && !empty($matches)) { foreach ($matches as $key => $val) { if (false !== strpos($url, ':' . $key)) { $url = str_replace(':' . $key, $val, $url); unset($matches[$key]); } } } if ($url instanceof \Closure) { // 执行闭包 $result = ['type' => 'function', 'function' => $url, 'params' => $matches]; } elseif (0 === strpos($url, '/') || 0 === strpos($url, 'http')) { // 路由到重定向地址 $result = ['type' => 'redirect', 'url' => $url, 'status' => isset($option['status']) ? $option['status'] : 301]; } elseif (0 === strpos($url, '\\')) { // 路由到方法 $method = strpos($url, '@') ? explode('@', $url) : $url; $result = ['type' => 'method', 'method' => $method, 'params' => $matches]; } elseif (0 === strpos($url, '@')) { // 路由到控制器 $result = ['type' => 'controller', 'controller' => substr($url, 1), 'params' => $matches]; } else { // 路由到模块/控制器/操作 $result = self::parseModule($url); } // 解析额外参数 self::parseUrlParams(empty($paths) ? '' : implode('/', $paths), $matches); // 记录匹配的路由信息 Request::instance()->routeInfo(['rule' => $rule, 'route' => $route, 'option' => $option]); return $result; } /** * 解析URL地址为 模块/控制器/操作 * @access private * @param string $url URL地址 * @param string $depr URL分隔符 * @return array */ private static function parseModule($url, $depr = '/') { list($path, $var) = self::parseUrlPath($url, $depr); $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; } $_GET = array_merge($_GET, $var); // 路由到模块/控制器/操作 return ['type' => 'module', 'module' => [$module, $controller, $action], 'convert' => false]; } /** * 解析URL地址中的参数Request对象 * @access private * @param string $rule 路由规则 * @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; } }