[TOC]
* * * * *
#1 路由解析
>>路由解析的實現在(library/Route.php)中
>路由 將Url分派到指定的應用處理邏輯部分。
>>Url路由 主要包括路由注冊與路由檢測兩部分
>路由注冊 注冊Url路由規則到Route中
>路由檢測 根據Route解析網絡請求Url到應用處理邏輯部分。
>注冊路由規則 , 指的是 (請求對象的url)與(應用層的應用地址)的對應關系
>路由規則的解析,就是在注冊的路由規則中查找請求對象url,對應的應用地址。
>由 [應用組織](http://www.hmoore.net/zmwtp/tp/210988) 章節可知,tp將應用組織為6種類型,在路由規則中,可以注冊5種類型的應用地址(module,controller,method,function,redirect,)為url的對應處理邏輯。
#2 路由規則
>> 路由規則主要實現Url信息與應用地址信息的關聯
>Url信息 主要是網絡請求的Url參數信息。如域名,請求類型,請求的控制器,操作,url參數等
>應用地址信息 主要是應用層對應的Url處理邏輯地址標識信息。
>根據應用組織的不同類型應用表示為不同形式的應用地址格式。
>路由規則的注冊形式 包括配置文件注冊,Route對應方法注冊。
>配置文件的注冊的實現分為加載路由配置文件,然后調用Route對應的注冊方法。
## 2-1 Url信息
>> Url信息可以由網絡請求對象(Request)的對應方法獲取
>具有信息查看[網絡請求(Request)](http://www.hmoore.net/zmwtp/tp/210989)章節
## 2-2 應用地址信息
>> 應用地址信息 根據不同形式的應用類型分為以下5種
>模塊/控制器地址
>重定向地址
>控制器方法
>類的方法
>閉包函數
### 1 模塊/控制器類應用地址
>> 模塊/控制器應用是最為常見的基礎應用類型。
> 模塊/控制器地址格式為 [模塊/控制器]操作?參數1=值1&參數2=值2...
~~~
; 配置文件格式
'blog/:id'=>'blog/read',
'blog/:id'=>'index/blog/read',
'blog/:id'=>'blog/read?status=1&app_id=5',
;Route方法格式
Route::rule('blog/:id','blog/read');
Route::rule('blog/:id','index/blog/read');
Route::rule('blog/:id','blog/read?status=1&app_id=5');
~~~
### 2 重定向類應用地址
>>重定向應用必須以"/"或者"http"開頭
~~~
; 重定向地址格式
'blog/:id'=>'/blog/read/id/:id'
'blog/:id'=>'http://blog.thinkphp.cn/read/:id'
Route::rule('blog/:id','/blog/read/id/:id');
Route::rule('blog/:id','http://blog.thinkphp.cn/read/:id');
~~~
### 3 控制器操作類應用地址
>> Url信息直接解析到特定控制器的操作地址
>格式為:@[模塊/控制器/]操作
~~~
'blog/:id'=>'@index/blog/read',
Route::rule('blog/:id','@index/blog/read');
~~~
### 4 類的方法類應用地址
>>Url信息解析到類的方法
>格式為:\類的命名空間\類名@方法名或者\類的命名空間\類名::方法名
~~~
'blog/:id'=>'\app\index\service\Blog@read',
'blog/:id'=>'\app\index\service\Blog::read',
Route::rule('blog/:id',''\app\index\service\Blog@read');
Route::rule('blog/:id',''\app\index\service\Blog::read');
~~~
### 5 閉包函數類應用地址
>> Url信息直接執行閉包函數應用
> 將閉包函數作為參數
~~~
Route::get('hello',function(){
return 'hello,world!';
});
Route::get('hello/:name',function($name){
return 'Hello,'.$name;
});
~~~
## 2-3 注冊形式
### 配置文件注冊
>>配置文件注冊在application/route.php中定義
~~~
return [
'new/:id' => 'News/read',
'blog/:id' => ['Blog/update',['method' => 'post|put'], ['id' => '\d+']],
];
~~~
### Route方法注冊
>>配置文件中也可以使用Route方法進行注冊
>最后需要返回一個數組作為配置文件的返回內容
~~~
;Route方法注冊路由
Route::rule([
['blog/:id','Blog/read',['method'=>'get']],
['blog/:id','Blog/update',['method'=>'post']],
...
],'','*',['ext'=>'html'],['id'=>'\d+']);
~~~
~~~
;兩種形式路由注冊
use think\Route;
Route::rule('hello/:name','index/index/hello');
return [
'new/:id' => 'News/read',
'blog/:id' => ['Blog/update',['method' => 'post|put'], ['id' => '\d+']],
];
~~~
>配置文件的位置可以自定義如下
~~~
// 定義路由配置文件(數組)
'route_config_file' => ['route', 'route1', 'route2'],
~~~
#3 路由注冊
>>路由規則的注冊按照層次分為三個類型
>類型注冊
>注冊核心
>參數選項
## 3-1 類型注冊
### get類型
~~~
public static function get($rule, $route = '', $option = [], $pattern = [])
{
self::rule($rule, $route, 'GET', $option, $pattern);
}
~~~
### post類型
~~~
public static function get($rule, $route = '', $option = [], $pattern = [])
{
self::rule($rule, $route, 'GET', $option, $pattern);
}
~~~
### put類型
~~~
public static function put($rule, $route = '', $option = [], $pattern = [])
{
self::rule($rule, $route, 'PUT', $option, $pattern);
}
~~~
### delete類型
~~~
public static function delete($rule, $route = '', $option = [], $pattern = [])
{
self::rule($rule, $route, 'DELETE', $option, $pattern);
}
~~~
### any類型
~~~
public static function any($rule, $route = '', $option = [], $pattern = [])
{
self::rule($rule, $route, '*', $option, $pattern);
}
~~~
### patch類型
~~~
public static function patch($rule, $route = '', $option = [], $pattern = [])
{
self::rule($rule, $route, 'PATCH', $option, $pattern);
}
~~~
### resource類型
~~~
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 . '$', $route . '/' . $val[2], $val[0], $option, $pattern);
}
}
}
~~~
### miss類型
~~~
public static function miss($route, $method = '*', $option = [])
{
self::rule('__miss__', $route, $method, $option, []);
}
~~~
### auto類型
~~~
public static function auto($route)
{
self::rule('__auto__', $route, '*', [], []);
}
~~~
##3-2 注冊核心
>>各種類型的路由注冊最后都會調用路由的注冊核心(Route::rule())
>Route::rule()會進一步調用注冊的實現Route::setRule()
### Route::rule()
~~~
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 = strtoupper($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 {
$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);
}
}
~~~
### Route::setRule()
~~~
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;
$rule = substr($rule, 0, -1);
}
} elseif (empty($option['complete_match']) && '$' == substr($rule, -1, 1)) {
// 是否完整匹配
$option['complete_match'] = true;
$rule = substr($rule, 0, -1);
}
if ('/' != $rule) {
$rule = trim($rule, '/');
}
$vars = self::parseVar($rule);
if (isset($name)) {
self::name($name, [$rule, $vars, self::$domain]);
}
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) {
self::$rules['domain'][self::$domain][$method][$rule] = true;
} else {
self::$rules[$method][$rule] = true;
}
}
}
}
}
~~~
## 3-3 參數選項
#4 注冊擴展
>>路由注冊還包含其他各種擴展信息
>域名的注冊
>路由分組注冊
>路由的綁定注冊
>路由的快捷注冊
>路由變量規則
>rest方法規則修改
>路由別名注冊
## 4-1 domain域名注冊
>域名注冊包括域名注冊接口(Route::domain())和域名注冊實現(Route::setDomain())
### 1 Route::domain()
~~~
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];
}
}
~~~
### Route::setDomain()
~~~
private static function setDomain($domain)
{
self::$domain = $domain;
}
~~~
## 4-2 路由分組
>> 路由分組的注冊包括路由分組接口(Route::group())和路由分組實現核心(Route::setGroup())
### Route::group()
~~~
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 = [];
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);
}
$vars = self::parseVar($key);
$item[] = ['rule' => $key, 'route' => $route, 'var' => $vars, 'option' => $options, 'pattern' => $patterns];
// 設置路由標識
self::name($route, [$key, $vars, self::$domain]);
}
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);
}
}
~~~
### Route::setGroup()
~~~
public static function setGroup($name, $option = [], $pattern = [])
{
self::$group['name'] = $name;
self::$group['option'] = $option ?: [];
self::$group['pattern'] = $pattern ?: [];
}
~~~
## 4-3 路由綁定注冊
>>路由綁定注冊將當前url綁定到特定命名空間(namespace),模塊(module),類(class),控制器(controller)
>包括路由綁定接口(Route::bind())和各種類型綁定(Route::bindToxx())
### Route::bind()
~~~
public static function bind($bind, $type = 'module')
{
self::$bind = ['type' => $type, $type => $bind];
}
~~~
### Route::bindToNamespace()
~~~
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]];
}
~~~
### Route::bindToModule()
~~~
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];
}
~~~
### Route::bindToClass()
~~~
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]];
}
~~~
### Route::bindToCOntroller()
~~~
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];
}
~~~
## 4-4 路由快捷注冊
>>路由快捷注冊包括快捷接口(Route::controller)和請求類型的前綴注冊(Route::setMethodPrefix)
### Route::controller()
~~~
public static function controller($rule, $route = '', $option = [], $pattern = [])
{
foreach (self::$methodPrefix as $type => $val) {
self::$type($rule . '/:action', $route . '/' . $val . ':action', $option, $pattern);
}
}
~~~
### Route::setMethodPrefix()
>>快捷路由的方法前綴默認為
~~~
private static $methodPrefix = [
'GET' => 'get',
'POST' => 'post',
'PUT' => 'put',
'DELETE' => 'delete',
];
~~~
>可以使用Route::setMethodPrefix()重定義快捷路由方法前綴
~~~
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;
}
}
~~~
## 4-5 路由變量規則
>>Route::pattern()注冊和獲取變量正則規則
### Route::pattern()
~~~
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;
}
}
~~~
## 4-6 rest方法格式修改
>>Route::rest()修改rest默認的規則格式
>默認的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'],
];
~~~
>可以使用Route::rest()方法重定義rest方法格式
### Route::rest()
~~~
public static function rest($name, $resource = [])
{
if (is_array($name)) {
self::$rest = array_merge(self::$rest, $name);
} else {
self::$rest[$name] = $resource;
}
}
~~~
## 4-7 路由別名注冊
>>Route::alias()可以注冊路由的別名
### Route::alias()
~~~
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;
}
}
~~~
#5 路由解析
>>路由解析 根據Url信息查找獲取對應的應用地址。
>按照查找的層次分為以下
>Url有效性檢測
>Url對應路由規則查找
>Url參數解析
## 5-1 Url有效性檢測
>>Url有效性檢測,包括以下
>Url檢測入口Route::check()
>路由別名檢測Route::checkRouteAlias()
>Url域名檢測Route::checkDomain()
>Url綁定檢測Route::checkBind()
### Route::check()
~~~
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;
}
}
$method = $request->method();
// 獲取當前請求類型的路由規則
$rules = 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, '/');
}
if (isset($rules[$url])) {
// 靜態路由規則檢測
$rule = $rules[$url];
if (true === $rule) {
$rule = self::getRouteExpress($url);
}
if (!empty($rule['route'])) {
return self::parseRule($url, $rule['route'], $url, $rule['option']);
}
}
// 路由規則檢測
if (!empty($rules)) {
return self::checkRoute($request, $rules, $url, $depr);
}
return false;
}
~~~
### Route::checkRouteAlias()
>>路由別名檢測checkRouteAlias()中
>首先獲取別名對應的路由規則
>然后檢查參數的有效性Route::checkOption()
>最后進行Url格式中綁定類型的檢測
~~~
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);
}
}
~~~
### Route::checkDomain()
>>域名規則檢測checkDomain()中
>首先獲取網絡請求域名(host)對應的注冊的域名規則(rules['domain])
>然后將url相關參數拼接到新的域名規則url中
>最后進行綁定類型的檢測與轉換
~~~
public static function checkDomain($request, &$currentRules, $method = 'GET')
{
// 域名規則
$rules = self::$rules['domain'];
// 開啟子域名部署 支持二級和三級域名
if (!empty($rules)) {
$host = $request->host();
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($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['*'];
}
}
}
}
~~~
### Route::checkUrlBind()
>>Url綁定檢測checkUrlBind()中
>首先獲取綁定類型
>然后根據綁定類型解析為不同的綁定規則
~~~
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 'namespace':
// 綁定到命名空間
return self::bindToNamespace($url, $bind, $depr);
case 'module':
// 如果有模塊/控制器綁定 針對路由到 模塊/控制器 有效
$url = (empty(self::$domainBind) ? $bind . '/' : '') . ltrim($url, '/');
break;
}
}
return false;
}
~~~
## 5-2 Url路由規則查找
>>Url路由規則查找包括靜態路由查找和非靜態路由查找
>靜態路由檢測Route::getRouteExpress()
>Url規則解析Route::parseRule()
>路由規則檢測Route::checkRoute()
### Route::getRouteExpress()
>>靜態路由檢測 url直接對應的路由規則
>首先獲取注冊的靜態路由規則
>然后調用parseRule()解析路由規則
~~~
private static function getRouteExpress($key)
{
return self::$domainRule ? self::$domainRule['*'][$key] : self::$rules['*'][$key];
}
~~~
### Route::parseRule()
>>路由規則解析parseRule()
>首先解析路由規則的url參數信息
>然后解析路由規則的對應Option選項參數到路由地址
>檢測是否綁定到模型
>然后合并Url的額外參數
>記錄路由信息到網絡請求
>執行路由檢查的后調after_behavior鉤子
>最后根據地址route的不同類型組裝不同類型的應用路由
~~~
private static function parseRule($rule, $route, $pathinfo, $option = [], $matches = [], $merge = false)
{
// 解析路由規則變量
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);
}
// 獲取路由地址規則
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);
unset($matches[$key]);
}
}
}
// 綁定模型數據
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::instance()->bind($bind);
}
// 解析額外參數
self::parseUrlParams(empty($paths) ? '' : implode('/', $paths), $matches);
// 記錄匹配的路由信息
Request::instance()->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, '/') || 0 === strpos($route, 'http')) {
// 路由到重定向地址
$result = ['type' => 'redirect', 'url' => $route, 'status' => isset($option['status']) ? $option['status'] : 301];
} elseif (0 === strpos($route, '\\')) {
// 路由到方法
$method = strpos($route, '@') ? explode('@', $route) : $route;
$result = ['type' => 'method', 'method' => $method];
} elseif (0 === strpos($route, '@')) {
// 路由到控制器
$result = ['type' => 'controller', 'controller' => substr($route, 1)];
} else {
// 路由到模塊/控制器/操作
$result = self::parseModule($route);
}
return $result;
}
~~~
### Route::checkRoute()
>>如果Url直接對應的路由規則為空。
>則進行miss和auto路由檢測
~~~
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, $url, $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 !== strpos($url, $str)) {
continue;
}
$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, '/') : '');
}
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);
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;
}
~~~
## 5-3 Url參數解析
>>Url參數解析是在路由解析過程中調用的子過程,包括以下
>Url解析入口
>UrlPath解析
>Url參數解析
>Module解析
### Route::parseUrl()
~~~
public static function parseUrl($url, $depr = '/', $autoSearch = false)
{
if (isset(self::$bind['module'])) {
// 如果有模塊/控制器綁定
$url = self::$bind['module'] . '/' . ltrim($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];
if (isset(self::$rules['name'][implode($depr, $route)])) {
throw new HttpException(404, 'invalid request:' . $url);
}
}
return ['type' => 'module', 'module' => $route];
}
~~~
### Route::parseUrlPath()
~~~
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];
}
~~~
### Route::parseUrlParams()
~~~
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);
}
~~~
### Route::parseModule()
>>在模塊/控制/操作類型的應用中調用parseModule()
>解析url到對應的類型應用
~~~
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;
}
// 設置當前請求的路由變量
Request::instance()->route($var);
// 路由到模塊/控制器/操作
return ['type' => 'module', 'module' => [$module, $controller, $action], 'convert' => false];
}
~~~
### Route::parseVar()
>>parseVar()解析規則的變量信息
>默認的變量規則為?,[:,:三種
>也可以注冊變量規則
~~~
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;
}
~~~
- 框架簡介
- 簡介
- 框架目錄
- 根目錄
- 應用目錄
- 核心目錄
- 擴展目錄
- 其他目錄
- 框架流程
- 啟動流程
- 請求流程
- 響應流程
- 框架結構
- 應用組織
- 網絡請求
- 路由組織
- 數據驗證
- 數據模型(M)
- 數據庫連接(Connection)
- 數據庫(Db)
- 查詢構造(Builder)
- 數據庫查詢(Query)
- 模型(Model)
- 模板視圖(V)
- 視圖(View)
- 模板引擎(Think)
- 模板標簽庫(TagLib)
- 控制器(C)
- 網絡響應
- 配置與緩存
- 配置操作
- 緩存操作
- cookie與session
- Cookie操作
- Session操作
- 自動加載
- 鉤子注冊
- 文件上傳
- 分頁控制
- 控制臺
- 自動構建
- 日志異常調試
- 異常處理
- 代碼調試
- 日志記錄
- 框架使用
- 1 環境搭建(Server)
- 2 網絡請求(Request)
- 3 請求路由(Route)
- 4 響應輸出(Response)
- 5 業務處理(Controller)
- 6 數據存取(Model)
- 7 Web界面(View)