-
流程(下载)
-
相关函数
1.path
/**
* 获取当前请求URL的pathinfo信息(不含URL后缀)
* @access public
* @return string
*/
public function path()
{
if (is_null($this->path)) {
$suffix = Config::get('url_html_suffix');
$pathinfo = $this->pathinfo();
if (false === $suffix) {
// 禁止伪静态访问
$this->path = $pathinfo;
} elseif ($suffix) {
// 去除正常的URL后缀
$this->path = preg_replace('/\.(' . ltrim($suffix, '.') . ')$/i', '', $pathinfo);
} else {
// 允许任何后缀访问
$this->path = preg_replace('/\.' . $this->ext() . '$/i', '', $pathinfo);
}
}
return $this->path;
}
2.pathinfo
/**
* 获取当前请求URL的pathinfo信息(含URL后缀)
* @access public
* @return string
*/
public function pathinfo()
{
if (is_null($this->pathinfo)) {
if (isset($_GET[Config::get('var_pathinfo')])) {
// 判断URL里面是否有兼容模式参数
$_SERVER['PATH_INFO'] = $_GET[Config::get('var_pathinfo')];
unset($_GET[Config::get('var_pathinfo')]);
} elseif (IS_CLI) {
// CLI模式下 index.php module/controller/action/params/...
$_SERVER['PATH_INFO'] = isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : '';
}
// 分析PATHINFO信息
if (!isset($_SERVER['PATH_INFO'])) {
foreach (Config::get('pathinfo_fetch') as $type) {
if (!empty($_SERVER[$type])) {
$_SERVER['PATH_INFO'] = (0 === strpos($_SERVER[$type], $_SERVER['SCRIPT_NAME'])) ?
substr($_SERVER[$type], strlen($_SERVER['SCRIPT_NAME'])) : $_SERVER[$type];
break;
}
}
}
$this->pathinfo = empty($_SERVER['PATH_INFO']) ? '/' : ltrim($_SERVER['PATH_INFO'], '/');
}
return $this->pathinfo;
}
3.导入配置文件
/**
* 导入配置文件的路由规则
* @access public
* @param array $rule 路由规则
* @param string $type 请求类型
* @return void
*/
public static function import(array $rule, $type = '*')
{
// 检查域名部署
if (isset($rule['__domain__'])) {
self::domain($rule['__domain__']);
unset($rule['__domain__']);
}
// 检查变量规则
if (isset($rule['__pattern__'])) {
self::pattern($rule['__pattern__']);
unset($rule['__pattern__']);
}
// 检查路由别名
if (isset($rule['__alias__'])) {
self::alias($rule['__alias__']);
unset($rule['__alias__']);
}
// 检查资源路由
if (isset($rule['__rest__'])) {
self::resource($rule['__rest__']);
unset($rule['__rest__']);
}
self::registerRules($rule, strtolower($type));
}
4.批量注册
/**
* 导入配置文件的路由规则
* @access public
* @param array $rule 路由规则
* @param string $type 请求类型
* @return void
*/
public static function import(array $rule, $type = '*')
{
// 检查域名部署
if (isset($rule['__domain__'])) {
self::domain($rule['__domain__']);
unset($rule['__domain__']);
}
// 检查变量规则
if (isset($rule['__pattern__'])) {
self::pattern($rule['__pattern__']);
unset($rule['__pattern__']);
}
// 检查路由别名
if (isset($rule['__alias__'])) {
self::alias($rule['__alias__']);
unset($rule['__alias__']);
}
// 检查资源路由
if (isset($rule['__rest__'])) {
self::resource($rule['__rest__']);
unset($rule['__rest__']);
}
self::registerRules($rule, strtolower($type));
}
5.设置路由规则
/**
* 设置路由规则
* @access public
* @param string|array $rule 路由规则
* @param string $route 路由地址
* @param string $type 请求类型
* @param array $option 路由参数
* @param array $pattern 变量规则
* @param string $group 所属分组
* @return void
*/
protected static function setRule($rule, $route, $type = '*', $option = [], $pattern = [], $group = '')
{
if (is_array($rule)) {
$name = $rule[0];
$rule = $rule[1];
} elseif (is_string($route)) {
$name = $route;
}
if (!isset($option['complete_match'])) {
if (Config::get('route_complete_match')) {
$option['complete_match'] = true;
} elseif ('$' == substr($rule, -1, 1)) {
// 是否完整匹配
$option['complete_match'] = true;
}
} elseif (empty($option['complete_match']) && '$' == substr($rule, -1, 1)) {
// 是否完整匹配
$option['complete_match'] = true;
}
if ('$' == substr($rule, -1, 1)) {
$rule = substr($rule, 0, -1);
}
if ('/' != $rule || $group) {
$rule = trim($rule, '/');
}
$vars = self::parseVar($rule);
if (isset($name)) {
$key = $group ? $group . ($rule ? '/' . $rule : '') : $rule;
$suffix = isset($option['ext']) ? $option['ext'] : null;
self::name($name, [$key, $vars, self::$domain, $suffix]);
}
if (isset($option['modular'])) {
$route = $option['modular'] . '/' . $route;
}
if ($group) {
if ('*' != $type) {
$option['method'] = $type;
}
if (self::$domain) {
self::$rules['domain'][self::$domain]['*'][$group]['rule'][] = ['rule' => $rule, 'route' => $route, 'var' => $vars, 'option' => $option, 'pattern' => $pattern];
} else {
self::$rules['*'][$group]['rule'][] = ['rule' => $rule, 'route' => $route, 'var' => $vars, 'option' => $option, 'pattern' => $pattern];
}
} else {
if ('*' != $type && isset(self::$rules['*'][$rule])) {
unset(self::$rules['*'][$rule]);
}
if (self::$domain) {
self::$rules['domain'][self::$domain][$type][$rule] = ['rule' => $rule, 'route' => $route, 'var' => $vars, 'option' => $option, 'pattern' => $pattern];
} else {
self::$rules[$type][$rule] = ['rule' => $rule, 'route' => $route, 'var' => $vars, 'option' => $option, 'pattern' => $pattern];
}
if ('*' == $type) {
// 注册路由快捷方式
foreach (['get', 'post', 'put', 'delete', 'patch', 'head', 'options'] as $method) {
if (self::$domain && !isset(self::$rules['domain'][self::$domain][$method][$rule])) {
self::$rules['domain'][self::$domain][$method][$rule] = true;
} elseif (!self::$domain && !isset(self::$rules[$method][$rule])) {
self::$rules[$method][$rule] = true;
}
}
}
}
}
6.注册路由分组
/**
* 注册路由分组
* @access public
* @param string|array $name 分组名称或者参数
* @param array|\Closure $routes 路由地址
* @param array $option 路由参数
* @param array $pattern 变量规则
* @return void
*/
public static function group($name, $routes, $option = [], $pattern = [])
{
if (is_array($name)) {
$option = $name;
$name = isset($option['name']) ? $option['name'] : '';
}
// 分组
$currentGroup = self::getGroup('name');
if ($currentGroup) {
$name = $currentGroup . ($name ? '/' . ltrim($name, '/') : '');
}
if (!empty($name)) {
if ($routes instanceof \Closure) {
$currentOption = self::getGroup('option');
$currentPattern = self::getGroup('pattern');
self::setGroup($name, array_merge($currentOption, $option), array_merge($currentPattern, $pattern));
call_user_func_array($routes, []);
self::setGroup($currentGroup, $currentOption, $currentPattern);
if ($currentGroup != $name) {
self::$rules['*'][$name]['route'] = '';
self::$rules['*'][$name]['var'] = self::parseVar($name);
self::$rules['*'][$name]['option'] = $option;
self::$rules['*'][$name]['pattern'] = $pattern;
}
} else {
$item = [];
$completeMatch = Config::get('route_complete_match');
foreach ($routes as $key => $val) {
if (is_numeric($key)) {
$key = array_shift($val);
}
if (is_array($val)) {
$route = $val[0];
$option1 = array_merge($option, isset($val[1]) ? $val[1] : []);
$pattern1 = array_merge($pattern, isset($val[2]) ? $val[2] : []);
} else {
$route = $val;
}
$options = isset($option1) ? $option1 : $option;
$patterns = isset($pattern1) ? $pattern1 : $pattern;
if ('$' == substr($key, -1, 1)) {
// 是否完整匹配
$options['complete_match'] = true;
$key = substr($key, 0, -1);
} elseif ($completeMatch) {
$options['complete_match'] = true;
}
$key = trim($key, '/');
$vars = self::parseVar($key);
$item[] = ['rule' => $key, 'route' => $route, 'var' => $vars, 'option' => $options, 'pattern' => $patterns];
// 设置路由标识
$suffix = isset($options['ext']) ? $options['ext'] : null;
self::name($route, [$name . ($key ? '/' . $key : ''), $vars, self::$domain, $suffix]);
}
self::$rules['*'][$name] = ['rule' => $item, 'route' => '', 'var' => [], 'option' => $option, 'pattern' => $pattern];
}
foreach (['get', 'post', 'put', 'delete', 'patch', 'head', 'options'] as $method) {
if (!isset(self::$rules[$method][$name])) {
self::$rules[$method][$name] = true;
} elseif (is_array(self::$rules[$method][$name])) {
self::$rules[$method][$name] = array_merge(self::$rules['*'][$name], self::$rules[$method][$name]);
}
}
} elseif ($routes instanceof \Closure) {
// 闭包注册
$currentOption = self::getGroup('option');
$currentPattern = self::getGroup('pattern');
self::setGroup('', array_merge($currentOption, $option), array_merge($currentPattern, $pattern));
call_user_func_array($routes, []);
self::setGroup($currentGroup, $currentOption, $currentPattern);
} else {
// 批量注册路由
self::rule($routes, '', '*', $option, $pattern);
}
}
7.解析模块的URL地址
/**
* 解析模块的URL地址 [模块/控制器/操作?]参数1=值1&参数2=值2...
* @access public
* @param string $url URL地址
* @param string $depr URL分隔符
* @param bool $autoSearch 是否自动深度搜索控制器
* @return array
*/
public static function parseUrl($url, $depr = '/', $autoSearch = false)
{
if (isset(self::$bind['module'])) {
$bind = str_replace('/', $depr, self::$bind['module']);
// 如果有模块/控制器绑定
$url = $bind . ('.' != substr($bind, -1) ? $depr : '') . ltrim($url, $depr);
}
$url = str_replace($depr, '|', $url);
list($path, $var) = self::parseUrlPath($url);
$route = [null, null, null];
if (isset($path)) {
// 解析模块
$module = Config::get('app_multi_module') ? array_shift($path) : null;
if ($autoSearch) {
// 自动搜索控制器
$dir = APP_PATH . ($module ? $module . DS : '') . Config::get('url_controller_layer');
$suffix = App::$suffix || Config::get('controller_suffix') ? ucfirst(Config::get('url_controller_layer')) : '';
$item = [];
$find = false;
foreach ($path as $val) {
$item[] = $val;
$file = $dir . DS . str_replace('.', DS, $val) . $suffix . EXT;
$file = pathinfo($file, PATHINFO_DIRNAME) . DS . Loader::parseName(pathinfo($file, PATHINFO_FILENAME), 1) . EXT;
if (is_file($file)) {
$find = true;
break;
} else {
$dir .= DS . Loader::parseName($val);
}
}
if ($find) {
$controller = implode('.', $item);
$path = array_slice($path, count($item));
} else {
$controller = array_shift($path);
}
} else {
// 解析控制器
$controller = !empty($path) ? array_shift($path) : null;
}
// 解析操作
$action = !empty($path) ? array_shift($path) : null;
// 解析额外参数
self::parseUrlParams(empty($path) ? '' : implode('|', $path));
// 封装路由
$route = [$module, $controller, $action];
// 检查地址是否被定义过路由
$name = strtolower($module . '/' . Loader::parseName($controller, 1) . '/' . $action);
$name2 = '';
if (empty($module) || isset($bind) && $module == $bind) {
$name2 = strtolower(Loader::parseName($controller, 1) . '/' . $action);
}
if (isset(self::$rules['name'][$name]) || isset(self::$rules['name'][$name2])) {
throw new HttpException(404, 'invalid request:' . str_replace('|', $depr, $url));
}
}
return ['type' => 'module', 'module' => $route];
}
8.解析URL的pathinfo参数和变量
/**
* 解析URL的pathinfo参数和变量
* @access private
* @param string $url URL地址
* @return array
*/
private static function parseUrlPath($url)
{
// 分隔符替换 确保路由定义使用统一的分隔符
$url = str_replace('|', '/', $url);
$url = trim($url, '/');
$var = [];
if (false !== strpos($url, '?')) {
// [模块/控制器/操作?]参数1=值1&参数2=值2...
$info = parse_url($url);
$path = explode('/', $info['path']);
parse_str($info['query'], $var);
} elseif (strpos($url, '/')) {
// [模块/控制器/操作]
$path = explode('/', $url);
} else {
$path = [$url];
}
return [$path, $var];
}
9.检测URL和规则路由是否匹配
/**
* 检测URL和规则路由是否匹配
* @access private
* @param string $url URL地址
* @param string $rule 路由规则
* @param array $pattern 变量规则
* @return array|false
*/
private static function match($url, $rule, $pattern)
{
$m2 = explode('/', $rule);
$m1 = explode('|', $url);
$var = [];
foreach ($m2 as $key => $val) {
// val中定义了多个变量 <id><name>
if (false !== strpos($val, '<') && preg_match_all('/<(\w+(\??))>/', $val, $matches)) {
$value = [];
$replace = [];
foreach ($matches[1] as $name) {
if (strpos($name, '?')) {
$name = substr($name, 0, -1);
$replace[] = '(' . (isset($pattern[$name]) ? $pattern[$name] : '\w+') . ')?';
} else {
$replace[] = '(' . (isset($pattern[$name]) ? $pattern[$name] : '\w+') . ')';
}
$value[] = $name;
}
$val = str_replace($matches[0], $replace, $val);
if (preg_match('/^' . $val . '$/', isset($m1[$key]) ? $m1[$key] : '', $match)) {
array_shift($match);
foreach ($value as $k => $name) {
if (isset($match[$k])) {
$var[$name] = $match[$k];
}
}
continue;
} else {
return false;
}
}
if (0 === strpos($val, '[:')) {
// 可选参数
$val = substr($val, 1, -1);
$optional = true;
} else {
$optional = false;
}
if (0 === strpos($val, ':')) {
// URL变量
$name = substr($val, 1);
if (!$optional && !isset($m1[$key])) {
return false;
}
if (isset($m1[$key]) && isset($pattern[$name])) {
// 检查变量规则
if ($pattern[$name] instanceof \Closure) {
$result = call_user_func_array($pattern[$name], [$m1[$key]]);
if (false === $result) {
return false;
}
} elseif (!preg_match(0 === strpos($pattern[$name], '/') ? $pattern[$name] : '/^' . $pattern[$name] . '$/', $m1[$key])) {
return false;
}
}
$var[$name] = isset($m1[$key]) ? $m1[$key] : '';
} elseif (!isset($m1[$key]) || 0 !== strcasecmp($val, $m1[$key])) {
return false;
}
}
// 成功匹配后返回URL中的动态变量数组
return $var;
}
10.解析规则路由
/**
* 解析规则路由
* @access private
* @param string $rule 路由规则
* @param string $route 路由地址
* @param string $pathinfo URL地址
* @param array $option 路由参数
* @param array $matches 匹配的变量
* @param bool $fromCache 通过缓存解析
* @return array
*/
private static function parseRule($rule, $route, $pathinfo, $option = [], $matches = [], $fromCache = false)
{
$request = Request::instance();
//保存解析缓存
if (Config::get('route_check_cache') && !$fromCache) {
try {
$key = self::getCheckCacheKey($request);
Cache::tag('route_check')->set($key, [$rule, $route, $pathinfo, $option, $matches]);
} catch (\Exception $e) {
}
}
// 解析路由规则
if ($rule) {
$rule = explode('/', $rule);
// 获取URL地址中的参数
$paths = explode('|', $pathinfo);
foreach ($rule as $item) {
$fun = '';
if (0 === strpos($item, '[:')) {
$item = substr($item, 1, -1);
}
if (0 === strpos($item, ':')) {
$var = substr($item, 1);
$matches[$var] = array_shift($paths);
} else {
// 过滤URL中的静态变量
array_shift($paths);
}
}
} else {
$paths = explode('|', $pathinfo);
}
// 获取路由地址规则
if (is_string($route) && isset($option['prefix'])) {
// 路由地址前缀
$route = $option['prefix'] . $route;
}
// 替换路由地址中的变量
if (is_string($route) && !empty($matches)) {
foreach ($matches as $key => $val) {
if (false !== strpos($route, ':' . $key)) {
$route = str_replace(':' . $key, $val, $route);
}
}
}
// 绑定模型数据
if (isset($option['bind_model'])) {
$bind = [];
foreach ($option['bind_model'] as $key => $val) {
if ($val instanceof \Closure) {
$result = call_user_func_array($val, [$matches]);
} else {
if (is_array($val)) {
$fields = explode('&', $val[1]);
$model = $val[0];
$exception = isset($val[2]) ? $val[2] : true;
} else {
$fields = ['id'];
$model = $val;
$exception = true;
}
$where = [];
$match = true;
foreach ($fields as $field) {
if (!isset($matches[$field])) {
$match = false;
break;
} else {
$where[$field] = $matches[$field];
}
}
if ($match) {
$query = strpos($model, '\\') ? $model::where($where) : Loader::model($model)->where($where);
$result = $query->failException($exception)->find();
}
}
if (!empty($result)) {
$bind[$key] = $result;
}
}
$request->bind($bind);
}
if (!empty($option['response'])) {
Hook::add('response_send', $option['response']);
}
// 解析额外参数
self::parseUrlParams(empty($paths) ? '' : implode('|', $paths), $matches);
// 记录匹配的路由信息
$request->routeInfo(['rule' => $rule, 'route' => $route, 'option' => $option, 'var' => $matches]);
// 检测路由after行为
if (!empty($option['after_behavior'])) {
if ($option['after_behavior'] instanceof \Closure) {
$result = call_user_func_array($option['after_behavior'], []);
} else {
foreach ((array) $option['after_behavior'] as $behavior) {
$result = Hook::exec($behavior, '');
if (!is_null($result)) {
break;
}
}
}
// 路由规则重定向
if ($result instanceof Response) {
return ['type' => 'response', 'response' => $result];
} elseif (is_array($result)) {
return $result;
}
}
if ($route instanceof \Closure) {
// 执行闭包
$result = ['type' => 'function', 'function' => $route];
} elseif (0 === strpos($route, '/') || strpos($route, '://')) {
// 路由到重定向地址
$result = ['type' => 'redirect', 'url' => $route, 'status' => isset($option['status']) ? $option['status'] : 301];
} elseif (false !== strpos($route, '\\')) {
// 路由到方法
list($path, $var) = self::parseUrlPath($route);
$route = str_replace('/', '@', implode('/', $path));
$method = strpos($route, '@') ? explode('@', $route) : $route;
$result = ['type' => 'method', 'method' => $method, 'var' => $var];
} elseif (0 === strpos($route, '@')) {
// 路由到控制器
$route = substr($route, 1);
list($route, $var) = self::parseUrlPath($route);
$result = ['type' => 'controller', 'controller' => implode('/', $route), 'var' => $var];
$request->action(array_pop($route));
$request->controller($route ? array_pop($route) : Config::get('default_controller'));
$request->module($route ? array_pop($route) : Config::get('default_module'));
App::$modulePath = APP_PATH . (Config::get('app_multi_module') ? $request->module() . DS : '');
} else {
// 路由到模块/控制器/操作
$result = self::parseModule($route, isset($option['convert']) ? $option['convert'] : false);
}
// 开启请求缓存
if ($request->isGet() && isset($option['cache'])) {
$cache = $option['cache'];
if (is_array($cache)) {
list($key, $expire, $tag) = array_pad($cache, 3, null);
} else {
$key = str_replace('|', '/', $pathinfo);
$expire = $cache;
$tag = null;
}
$request->cache($key, $expire, $tag);
}
return $result;
}
11.解析URL地址为 模块/控制器/操作
/**
* 解析URL地址为 模块/控制器/操作
* @access private
* @param string $url URL地址
* @param bool $convert 是否自动转换URL地址
* @return array
*/
private static function parseModule($url, $convert = false)
{
list($path, $var) = self::parseUrlPath($url);
$action = array_pop($path);
$controller = !empty($path) ? array_pop($path) : null;
$module = Config::get('app_multi_module') && !empty($path) ? array_pop($path) : null;
$method = Request::instance()->method();
if (Config::get('use_action_prefix') && !empty(self::$methodPrefix[$method])) {
// 操作方法前缀支持
$action = 0 !== strpos($action, self::$methodPrefix[$method]) ? self::$methodPrefix[$method] . $action : $action;
}
// 设置当前请求的路由变量
Request::instance()->route($var);
// 路由到模块/控制器/操作
return ['type' => 'module', 'module' => [$module, $controller, $action], 'convert' => $convert];
}
12.解析URL地址中的参数Request对象
/**
* 解析URL地址中的参数Request对象
* @access private
* @param string $url 路由规则
* @param array $var 变量
* @return void
*/
private static function parseUrlParams($url, &$var = [])
{
if ($url) {
if (Config::get('url_param_type')) {
$var += explode('|', $url);
} else {
preg_replace_callback('/(\w+)\|([^\|]+)/', function ($match) use (&$var) {
$var[$match[1]] = strip_tags($match[2]);
}, $url);
}
}
// 设置当前请求的参数
Request::instance()->route($var);
}
13.分析路由规则中的变量
// 分析路由规则中的变量
private static function parseVar($rule)
{
// 提取路由规则中的变量
$var = [];
foreach (explode('/', $rule) as $val) {
$optional = false;
if (false !== strpos($val, '<') && preg_match_all('/<(\w+(\??))>/', $val, $matches)) {
foreach ($matches[1] as $name) {
if (strpos($name, '?')) {
$name = substr($name, 0, -1);
$optional = true;
} else {
$optional = false;
}
$var[$name] = $optional ? 2 : 1;
}
}
if (0 === strpos($val, '[:')) {
// 可选参数
$optional = true;
$val = substr($val, 1, -1);
}
if (0 === strpos($val, ':')) {
// URL变量
$name = substr($val, 1);
$var[$name] = $optional ? 2 : 1;
}
}
return $var;
}
14.用反射执行类的方法 支持参数绑定
/**
* 调用反射执行类的方法 支持参数绑定
* @access public
* @param string|array $method 方法
* @param array $vars 变量
* @return mixed
*/
public static function invokeMethod($method, $vars = [])
{
if (is_array($method)) {
$class = is_object($method[0]) ? $method[0] : self::invokeClass($method[0]);
$reflect = new \ReflectionMethod($class, $method[1]);
} else {
// 静态方法
$reflect = new \ReflectionMethod($method);
}
$args = self::bindParams($reflect, $vars);
self::$debug && Log::record('[ RUN ] ' . $reflect->class . '->' . $reflect->name . '[ ' . $reflect->getFileName() . ' ]', 'info');
return $reflect->invokeArgs(isset($class) ? $class : null, $args);
}
15.调用反射执行类的实例化 支持依赖注入
/**
* 调用反射执行类的实例化 支持依赖注入
* @access public
* @param string $class 类名
* @param array $vars 变量
* @return mixed
*/
public static function invokeClass($class, $vars = [])
{
$reflect = new \ReflectionClass($class);
$constructor = $reflect->getConstructor();
$args = $constructor ? self::bindParams($constructor, $vars) : [];
return $reflect->newInstanceArgs($args);
}