www.lightcourse.com/thinkphp/library/think/Route.php

1646 lines
60 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace think;
use think\exception\HttpException;
class Route
{
// 路由规则
private static $rules = [
'get' => [],
'post' => [],
'put' => [],
'delete' => [],
'patch' => [],
'head' => [],
'options' => [],
'*' => [],
'alias' => [],
'domain' => [],
'pattern' => [],
'name' => [],
];
// 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',
'patch' => 'patch',
];
// 子域名
private static $subDomain = '';
// 域名绑定
private static $bind = [];
// 当前分组信息
private static $group = [];
// 当前子域名绑定
private static $domainBind;
private static $domainRule;
// 当前域名
private static $domain;
// 当前路由执行过程中的参数
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 路由规则
* @param array $option 路由参数
* @param array $pattern 变量规则
* @return void
*/
public static function domain($domain, $rule = '', $option = [], $pattern = [])
{
if (is_array($domain)) {
foreach ($domain as $key => $item) {
self::domain($key, $item, $option, $pattern);
}
} elseif ($rule instanceof \Closure) {
// 执行闭包
self::setDomain($domain);
call_user_func_array($rule, []);
self::setDomain(null);
} elseif (is_array($rule)) {
self::setDomain($domain);
self::group('', function () use ($rule) {
// 动态注册域名的路由规则
self::registerRules($rule);
}, $option, $pattern);
self::setDomain(null);
} else {
self::$rules['domain'][$domain]['[bind]'] = [$rule, $option, $pattern];
}
}
private static function setDomain($domain)
{
self::$domain = $domain;
}
/**
* 设置路由绑定
* @access public
* @param mixed $bind 绑定信息
* @param string $type 绑定类型 默认为module 支持 namespace class controller
* @return mixed
*/
public static function bind($bind, $type = 'module')
{
self::$bind = ['type' => $type, $type => $bind];
}
/**
* 设置或者获取路由标识
* @access public
* @param string|array $name 路由命名标识 数组表示批量设置
* @param array $value 路由地址及变量信息
* @return array
*/
public static function name($name = '', $value = null)
{
if (is_array($name)) {
return self::$rules['name'] = $name;
} elseif ('' === $name) {
return self::$rules['name'];
} elseif (!is_null($value)) {
self::$rules['name'][strtolower($name)][] = $value;
} else {
$name = strtolower($name);
return isset(self::$rules['name'][$name]) ? self::$rules['name'][$name] : null;
}
}
/**
* 读取路由绑定
* @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__']);
}
self::registerRules($rule, strtolower($type));
}
// 批量注册路由
protected static function registerRules($rules, $type = '*')
{
foreach ($rules as $key => $val) {
if (is_numeric($key)) {
$key = array_shift($val);
}
if (empty($val)) {
continue;
}
if (is_string($key) && 0 === strpos($key, '[')) {
$key = substr($key, 1, -1);
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|array $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::getGroup('name');
if (!is_null($group)) {
// 路由分组
$option = array_merge(self::getGroup('option'), $option);
$pattern = array_merge(self::getGroup('pattern'), $pattern);
}
$type = strtolower($type);
if (strpos($type, '|')) {
$option['method'] = $type;
$type = '*';
}
if (is_array($rule) && empty($route)) {
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 {
$option1 = null;
$pattern1 = null;
$route = $val;
}
self::setRule($key, $route, $type, !is_null($option1) ? $option1 : $option, !is_null($pattern1) ? $pattern1 : $pattern, $group);
}
} else {
self::setRule($rule, $route, $type, $option, $pattern, $group);
}
}
/**
* 设置路由规则
* @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;
}
}
}
}
}
/**
* 设置当前执行的参数信息
* @access public
* @param array $options 参数信息
* @return mixed
*/
protected static function setOption($options = [])
{
self::$option[] = $options;
}
/**
* 获取当前执行的所有参数信息
* @access public
* @return array
*/
public static function getOption()
{
return self::$option;
}
/**
* 获取当前的分组信息
* @access public
* @param string $type 分组信息名称 name option pattern
* @return mixed
*/
public static function getGroup($type)
{
if (isset(self::$group[$type])) {
return self::$group[$type];
} else {
return 'name' == $type ? null : [];
}
}
/**
* 设置当前的路由分组
* @access public
* @param string $name 分组名称
* @param array $option 分组路由参数
* @param array $pattern 分组变量规则
* @return void
*/
public static function setGroup($name, $option = [], $pattern = [])
{
self::$group['name'] = $name;
self::$group['option'] = $option ?: [];
self::$group['pattern'] = $pattern ?: [];
}
/**
* 注册路由分组
* @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);
}
}
/**
* 注册路由
* @access public
* @param string|array $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|array $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|array $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|array $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|array $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|array $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|array $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], '/');
$option['rest'] = $key;
self::rule($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));
} else {
self::$methodPrefix[strtolower($method)] = $prefix;
}
}
/**
* rest方法定义和修改
* @access public
* @param string|array $name 方法名称
* @param array|bool $resource 资源
* @return void
*/
public static function rest($name, $resource = [])
{
if (is_array($name)) {
self::$rest = $resource ? $name : 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, []);
}
/**
* 注册一个自动解析的URL路由
* @access public
* @param string $route 路由地址
* @return void
*/
public static function auto($route)
{
self::rule('__auto__', $route, '*', [], []);
}
/**
* 获取或者批量设置路由定义
* @access public
* @param mixed $rules 请求类型或者路由定义数组
* @return array
*/
public static function rules($rules = '')
{
if (is_array($rules)) {
self::$rules = $rules;
} elseif ($rules) {
return true === $rules ? self::$rules : self::$rules[strtolower($rules)];
} else {
$rules = self::$rules;
unset($rules['pattern'], $rules['alias'], $rules['domain'], $rules['name']);
return $rules;
}
}
/**
* 检测子域名部署
* @access public
* @param Request $request Request请求对象
* @param array $currentRules 当前路由规则
* @param string $method 请求类型
* @return void
*/
public static function checkDomain($request, &$currentRules, $method = 'get')
{
// 域名规则
$rules = self::$rules['domain'];
// 开启子域名部署 支持二级和三级域名
if (!empty($rules)) {
$host = $request->host(true);
if (isset($rules[$host])) {
// 完整域名或者IP配置
$item = $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])) {
// 子域名配置
$item = $rules[$subDomain];
} elseif (isset($rules['*.' . $domain2]) && !empty($domain3)) {
// 泛三级域名
$item = $rules['*.' . $domain2];
$panDomain = $domain3;
} elseif (isset($rules['*']) && !empty($domain2)) {
// 泛二级域名
if ('www' != $domain2) {
$item = $rules['*'];
$panDomain = $domain2;
}
}
}
}
if (!empty($item)) {
if (isset($panDomain)) {
// 保存当前泛域名
$request->route(['__domain__' => $panDomain]);
}
if (isset($item['[bind]'])) {
// 解析子域名部署规则
list($rule, $option, $pattern) = $item['[bind]'];
if (!empty($option['https']) && !$request->isSsl()) {
// https检测
throw new HttpException(404, 'must use https request:' . $host);
}
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)];
} else {
// 绑定到模块/控制器 例如 index/user
self::$bind = ['type' => 'module', 'module' => $result];
}
self::$domainBind = true;
} else {
self::$domainRule = $item;
$currentRules = isset($item[$method]) ? $item[$method] : $item['*'];
}
}
}
}
/**
* 检测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 (!App::$debug && Config::get('route_check_cache')) {
$key = self::getCheckCacheKey($request);
if (Cache::has($key)) {
list($rule, $route, $pathinfo, $option, $matches) = Cache::get($key);
return self::parseRule($rule, $route, $pathinfo, $option, $matches, true);
}
}
// 分隔符替换 确保路由定义使用统一的分隔符
$url = str_replace($depr, '|', $url);
if (isset(self::$rules['alias'][$url]) || isset(self::$rules['alias'][strstr($url, '|', true)])) {
// 检测路由别名
$result = self::checkRouteAlias($request, $url, $depr);
if (false !== $result) {
return $result;
}
}
$method = strtolower($request->method());
// 获取当前请求类型的路由规则
$rules = isset(self::$rules[$method]) ? self::$rules[$method] : [];
// 检测域名部署
if ($checkDomain) {
self::checkDomain($request, $rules, $method);
}
// 检测URL绑定
$return = self::checkUrlBind($url, $rules, $depr);
if (false !== $return) {
return $return;
}
if ('|' != $url) {
$url = rtrim($url, '|');
}
$item = str_replace('|', '/', $url);
if (isset($rules[$item])) {
// 静态路由规则检测
$rule = $rules[$item];
if (true === $rule) {
$rule = self::getRouteExpress($item);
}
if (!empty($rule['route']) && self::checkOption($rule['option'], $request)) {
self::setOption($rule['option']);
return self::parseRule($item, $rule['route'], $url, $rule['option']);
}
}
// 路由规则检测
if (!empty($rules)) {
return self::checkRoute($request, $rules, $url, $depr);
}
return false;
}
private static function getRouteExpress($key)
{
return self::$domainRule ? self::$domainRule['*'][$key] : self::$rules['*'][$key];
}
/**
* 检测路由规则
* @access private
* @param Request $request
* @param array $rules 路由规则
* @param string $url URL地址
* @param string $depr URL分割符
* @param string $group 路由分组名
* @param array $options 路由参数(分组)
* @return mixed
*/
private static function checkRoute($request, $rules, $url, $depr = '/', $group = '', $options = [])
{
foreach ($rules as $key => $item) {
if (true === $item) {
$item = self::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中定义了多个变量 <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;
}
/**
* 解析规则路由
* @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;
}
}