// +---------------------------------------------------------------------- namespace Think; class Route { // 路由规则 static private $rules = [ 'GET' => [], 'POST' => [], 'PUT' => [], 'DELETE' => [], '*' => [], ]; // URL映射规则 static private $map = []; // 子域名部署规则 static private $domain = []; // 添加URL映射规则 static public function map($map, $route = ''){ if(is_array($map)) { self::$map = array_merge(self::$map, $map); }else{ self::$map[$map] = $route; } } // 添加子域名部署规则 static public function domain($domain, $rule = ''){ if(is_array($domain)) { self::$domain = array_merge(self::$domain,$domain); }else{ self::$domain[$domain] = $rule; } } // 注册路由规则 static public function register($rule, $route = '', $type = 'GET', $option = []){ if(strpos($type, '|')) { foreach (explode('|', $type) as $val){ self::register($rule, $route, $val, $option); } }else{ if(is_array($rule)) { foreach ($rule as $key => $val){ self::$rules[$type][$key] = ['route' => $val, 'option' => $option]; } }else{ self::$rules[$type][$rule] = ['route' => $route, 'option' => $option]; } } } // 注册任意请求的路由规则 static public function any($rule, $route = '', $option = []){ self::register($rule, $route, '*', $option); } // 注册get请求的路由规则 static public function get($rule, $route = '', $option = []){ self::register($rule, $route, 'GET', $option); } // 注册post请求的路由规则 static public function post($rule, $route = '', $option = []){ self::register($rule, $route, 'POST', $option); } // 注册put请求的路由规则 static public function put($rule, $route = '', $option = []){ self::register($rule, $route, 'PUT', $option); } // 注册delete请求的路由规则 static public function delete($rule, $route = '', $option = []){ self::register($rule, $route, 'DELETE', $option); } // 检测子域名部署 static public function checkDomain($config=[]){ // 开启子域名部署 支持二级和三级域名 if(!empty(self::$domain)) { $rules = self::$domain; if(isset($rules[$_SERVER['HTTP_HOST']])) { // 完整域名或者IP配置 $rule = $rules[$_SERVER['HTTP_HOST']]; }else{// 子域名配置 $domain = array_slice(explode('.', $_SERVER['HTTP_HOST']), 0, -2); if(!empty($domain)) { $subDomain = implode('.', $domain); $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)) { // 子域名部署规则 // '子域名'=>'模块名' // '子域名'=>['模块名','var1=a&var2=b&var3=*']; if($rule instanceof \Closure) { // 执行闭包并中止 self::invokeRule($rule); exit; } if(is_array($rule)) { $_GET[$config['var_module']] = $rule[0]; if(isset($rule[1])) { // 传入参数 parse_str($rule[1], $parms); if(isset($panDomain)) { $pos = array_search('*', $parms); if(false !== $pos) { // 泛域名作为参数 $parms[$pos] = $panDomain; } } $_GET = array_merge($_GET,$parms); } }else{ $_GET[$config['var_module']] = $rule; } } } } // 检测URL路由 static public function check($regx,$config) { // 优先检测是否存在PATH_INFO if(empty($regx)) $regx = '/' ; // 分隔符替换 确保路由定义使用统一的分隔符 if('/' != $config['pathinfo_depr']){ $regx = str_replace($config['pathinfo_depr'], '/', $regx); } if(isset(self::$map[$regx])) { // URL映射 return self::parseUrl(self::$map[$regx]); } // 获取当前请求类型的路由规则 $rules = self::$rules[REQUEST_METHOD]; if(!empty(self::$rules['*'])) { // 合并任意请求的路由规则 $rules = array_merge(self::$rules['*'], $rules); } // 路由规则检测 if(!empty($rules)) { foreach ($rules as $rule=>$val){ $route = $val['route']; $option = $val['option']; // 伪静态后缀检测 if(isset($option['ext']) && __EXT__ != $option['ext']) { continue; } // https检测 if(!empty($option['https']) && !self::isSsl()) { continue; } // 自定义检测 if(!empty($option['callback']) && is_callable($option['callback'])) { if(false === call_user_func($option['callback'])) { continue; } } if(0 === strpos($rule, '/') && preg_match($rule, $regx, $matches)) { // 正则路由 if(!empty($option['file'])){ // 调度到某个文件中执行 include $route; exit; } if($route instanceof \Closure) { // 执行闭包并中止 self::invokeRegx($route, $matches); exit; } return self::parseRegex($matches, $route, $regx,$config); }else{ // 规则路由 $len1 = substr_count($regx, '/'); $len2 = substr_count($rule, '/'); if($len1 >= $len2) { if('$' == substr($rule, -1, 1)) {// 完整匹配 if($len1 != $len2) { continue; }else{ $rule = substr($rule, 0, -1); } } if(false !== $var = self::match($regx, $rule)){ if(!empty($option['file'])){ // 调度到某个文件中执行 include $route; exit; } if($route instanceof \Closure) { // 执行闭包并中止 self::invokeRule($route, $var); exit; } return self::parseRule($rule, $route, $regx,$config); } } } } } return self::parseUrl($regx,$config); } /** * 判断是否SSL协议 * @return boolean */ static public function isSsl() { if(isset($_SERVER['HTTPS']) && ('1' == $_SERVER['HTTPS'] || 'on' == strtolower($_SERVER['HTTPS']))){ return true; }elseif(isset($_SERVER['SERVER_PORT']) && ('443' == $_SERVER['SERVER_PORT'] )) { return true; } return false; } // 执行正则匹配下的闭包方法 支持参数调用 static private function invokeRegx($closure, $var = []) { $reflect = new \ReflectionFunction($closure); $params = $reflect->getParameters(); $args = []; array_shift($var); foreach ($params as $param){ $name = $param->getName(); if(!empty($var)) { $args[] = array_shift($var); }elseif($param->isDefaultValueAvailable()){ $args[] = $param->getDefaultValue(); } } $reflect->invokeArgs($args); } // 执行规则匹配下的闭包方法 支持参数调用 static private function invokeRule($closure, $var = []) { $reflect = new \ReflectionFunction($closure); $params = $reflect->getParameters(); $args = []; foreach ($params as $param){ $name = $param->getName(); if(isset($var[$name])) { $args[] = $var[$name]; }elseif($param->isDefaultValueAvailable()){ $args[] = $param->getDefaultValue(); } } $reflect->invokeArgs($args); } // 解析模块的URL地址 [模块/]控制器/操作 static private function parseUrl($url,$config=[]) { if('/' == $url) { return ; } $paths = explode('/', $url); $_GET[$config['var_action']] = array_pop($paths); if(!defined('BIND_CONTROLLER') && !isset($_GET[$config['var_controller']])) { $_GET[$config['var_controller']] = array_pop($paths); } if(!defined('BIND_MODULE') && !isset($_GET[$config['var_module']])) { $_GET[$config['var_module']] = array_pop($paths); } // 解析剩余的URL参数 $var = []; if(!empty($paths)) { preg_replace_callback('/(\w+)\/([^\/]+)/', function($match) use(&$var){ $var[strtolower($match[1])]=strip_tags($match[2]);}, implode('/',$paths)); } $_GET = array_merge($var, $_GET); } // 解析规范的路由地址 // 地址格式 [控制器/操作?]参数1=值1&参数2=值2... static private function parseRoute($url,$config=[]) { $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{ // 参数1=值1&参数2=值2... parse_str($url, $var); } if(isset($path)) { $action = array_pop($path); $_GET[$config['var_action']] = '[rest]'==$action ? REQUEST_METHOD : $action; if(!empty($path)) { $_GET[$config['var_controller']] = array_pop($path); } if(!empty($path)) { $_GET[$config['var_module']] = array_pop($path); } } return $var; } // 检测URL和规则路由是否匹配 static private function match($regx, $rule) { $m1 = explode('/', $regx); $m2 = explode('/', $rule); $var = []; foreach ($m2 as $key=>$val){ if(0 === strpos($val, ':')) {// 动态变量 if(strpos($val, '\\')) { $type = substr($val, -1); if('d' == $type && !is_numeric($m1[$key])) { return false; } $name = substr($val, 1, -2); }elseif($pos = strpos($val, '^')){ $array = explode('|', substr(strstr($val, '^'), 1)); if(in_array($m1[$key], $array)) { return false; } $name = substr($val, 1, $pos - 1); }else{ $name = substr($val, 1); } $var[$name] = $m1[$key]; }elseif(0 !== strcasecmp($val, $m1[$key])){ return false; } } // 成功匹配后返回URL中的动态变量数组 return $var; } // 解析规则路由 // '路由规则'=>'[控制器/操作]?额外参数1=值1&额外参数2=值2...' // '路由规则'=>array('[控制器/操作]','额外参数1=值1&额外参数2=值2...') // '路由规则'=>'外部地址' // '路由规则'=>array('外部地址','重定向代码') // 路由规则中 :开头 表示动态变量 // 外部地址中可以用动态变量 采用 :1 :2 的方式 // 'news/:month/:day/:id'=>array('News/read?cate=1','status=1'), // 'new/:id'=>array('/new.php?id=:1',301), 重定向 static private function parseRule($rule, $route, $regx,$config) { // 获取路由地址规则 $url = is_array($route) ? $route[0] : $route; // 获取URL地址中的参数 $paths = explode('/', $regx); // 解析路由规则 $matches = []; $rule = explode('/', $rule); foreach ($rule as $item){ if(0 === strpos($item, ':')) { // 动态变量获取 if($pos = strpos($item, '^') ) { $var = substr($item, 1, $pos - 1); }elseif(strpos($item, '\\')){ $var = substr($item, 1, -2); }else{ $var = substr($item, 1); } $matches[$var] = array_shift($paths); }else{ // 过滤URL中的静态变量 array_shift($paths); } } if(0 === strpos($url, '/') || 0 === strpos($url, 'http')) { // 路由重定向跳转 if(strpos($url, ':')) { // 传递动态参数 $values = array_values($matches); $url = preg_replace('/:(\d+)/e', '$values[\\1-1]', $url); } header("Location: $url", true, (is_array($route) && isset($route[1])) ? $route[1] : 301); exit; }else{ // 解析路由地址 $var = self::parseRoute($url,$config); // 解析路由地址里面的动态参数 $values = array_values($matches); foreach ($var as $key => $val){ if(0 === strpos($val, ':')) { $var[$key] = $values[substr($val, 1) - 1]; } } $var = array_merge($matches, $var); // 解析剩余的URL参数 if(!empty($paths)) { preg_replace_callback('/(\w+)\/([^\/]+)/', function($match) use(&$var){ $var[strtolower($match[1])] = strip_tags($match[2]); }, implode('/',$paths)); } // 解析路由自动传人参数 if(is_array($route) && isset($route[1])) { parse_str($route[1], $params); $var = array_merge($var, $params); } $_GET = array_merge($var, $_GET); } } // 解析正则路由 // '路由正则'=>'[控制器/操作]?参数1=值1&参数2=值2...' // '路由正则'=>array('[控制器/操作]?参数1=值1&参数2=值2...','额外参数1=值1&额外参数2=值2...') // '路由正则'=>'外部地址' // '路由正则'=>array('外部地址','重定向代码') // 参数值和外部地址中可以用动态变量 采用 :1 :2 的方式 // '/new\/(\d+)\/(\d+)/'=>array('News/read?id=:1&page=:2&cate=1','status=1'), // '/new\/(\d+)/'=>array('/new.php?id=:1&page=:2&status=1','301'), 重定向 static private function parseRegex($matches, $route, $regx,$config) { // 获取路由地址规则 $url = is_array($route) ? $route[0] : $route; $url = preg_replace('/:(\d+)/e', '$matches[\\1]', $url); if(0 === strpos($url, '/') || 0 === strpos($url, 'http')) { // 路由重定向跳转 header("Location: $url", true, (is_array($route) && isset($route[1])) ? $route[1] : 301); exit; }else{ // 解析路由地址 $var = self::parseRoute($url,$config); // 解析剩余的URL参数 $regx = substr_replace($regx, '', 0, strlen($matches[0])); if($regx) { preg_replace_callback('/(\w+)\/([^\/]+)/', function($match) use(&$var){ $var[strtolower($match[1])] = strip_tags($match[2]); }, $regx); } // 解析路由自动传人参数 if(is_array($route) && isset($route[1])) { parse_str($route[1], $params); $var = array_merge($var, $params); } $_GET = array_merge($var, $_GET); } } }