:-: **1 路由解析**
>[danger] 在App的run()方法初始化后,開始進行請求路由解析
>
> 請求解析的過程就是講url根據注冊的路由分派到對應的應用層業務邏輯
>
> 請求解析的實現由/library/think/Route實現
* * * * *
:-: **2 路由解析入口源碼分析**
* * * * *
~~~
//App run()方法
// 初始化應用
$this->initialize();
if ($this->bind) {
// 模塊/控制器綁定
$this->route->bind($this->bind);
} elseif ($this->config('app.auto_bind_module')) {
// 入口自動綁定
$name = pathinfo($this->request->baseFile(), PATHINFO_FILENAME);
if ($name && 'index' != $name && is_dir($this->appPath . $name)) {
$this->route->bind($name);
}
}
~~~
>[danger] 首先檢查App的屬性bind,是否綁定到特定模塊/控制器
> 如果App的屬性bind沒有設置,則讀取配置的`app.auto_bind_module`。
> 如果設置了自動綁定模塊,則將入口文件名綁定為模塊名稱。
> 比如設置app的auto_bind_module為ture。則訪問admin.php入口文件。
> 那么默認的模塊就是admin模塊
~~~
// 監聽app_dispatch
$this->hook->listen('app_dispatch');
~~~
調用app_dispatch的回調函數
~~~
$dispatch = $this->dispatch;
if (empty($dispatch)) {
// 進行URL路由檢測
$this->route->lazy($this->config('app.url_lazy_route'));
$dispatch = $this->routeCheck();
}
$this->request->dispatch($dispatch);
~~~
>[danger] 獲取應用的調度信息。這里的routeCheck()是路由解析的入口
> 然后將解析的調度信息保存到全局Request對象中。
~~~
if ($this->debug) {
$this->log('[ ROUTE ] ' . var_export($this->request->routeInfo(), true));
$this->log('[ HEADER ] ' . var_export($this->request->header(), true));
$this->log('[ PARAM ] ' . var_export($this->request->param(), true));
}
~~~
>[danger] 調試模式下,保存路由的請求信息到日志文件中
~~~
// 監聽app_begin
$this->hook->listen('app_begin');
~~~
>[danger] 調用app_begin的回調函數
~~~
$this->request->cache(
$this->config('app.request_cache'),
$this->config('app.request_cache_expire'),
$this->config('app.request_cache_except')
);
~~~
>[danger]檢查否開啟了請求緩存,
>如果設置了緩存,則嘗試讀取緩存結果
~~~
// 執行調度
$data = $dispatch->run();
~~~
>[danger]這里執行調度。也就是執行請求分派到的業務邏輯,
>通常是模塊/控制器中的特定方法。也可以是其他形式的業務邏輯
>比如 閉包函數,或者直接返回模板等。
~~~
$this->middlewareDispatcher->add(function (Request $request, $next) use ($data) {
// 輸出數據到客戶端
if ($data instanceof Response) {
$response = $data;
} elseif (!is_null($data)) {
// 默認自動識別響應輸出類型
$isAjax = $request->isAjax();
$type = $isAjax ? $this->config('app.default_ajax_return') : $this->config('app.default_return_type');
$response = Response::create($data, $type);
} else {
$response = Response::create();
}
return $response;
});
$response = $this->middlewareDispatcher->dispatch($this->request);
~~~
>[danger] 這里是think5.1準備添加的中間件功能。
~~~
$this->hook->listen('app_end', $response);
return $response;
~~~
>[danger] 調度執行完后,調用app_end的回調函數
> 最后run()方法返回創建的響應對象Response
>然后在入口文件index.php中調用Response的send()將結果輸出到客戶端
`Container::get('app')->run()->send();`
:-: **3 路由注冊過程源碼分析**
>[danger] 由上面的分析可知 路由解析的入口是App的routeCheck()
> 下面開始分析App的routeCheck()是如何解析請求Request得到調度信息的
~~~
//App routeCheck()
$path = $this->request->path();
$depr = $this->config('app.pathinfo_depr');
~~~
>[danger]調用Request的path()方法獲取當前請求的pathinfo信息
>然后讀取app.pathinfo_depr獲取pathinfo的分隔符
>這里的path就是請求的url`index/blog/index`。pathifo_depr也就是url分隔符`/`
~~~
$files = scandir($this->routePath);
foreach ($files as $file) {
if (strpos($file, '.php')) {
$filename = $this->routePath . $file;
// 導入路由配置
$rules = include $filename;
if (is_array($rules)) {
$this->route->import($rules);
}
}
}
~~~
>[danger]這里遍歷路由目錄/route/中的所有文件。將其中的路由規則導入
>也就是將配置的路由信息加載到框架中。
>然后根據注冊的路由信息匹配請求url
~~~
if ($this->config('app.route_annotation')) {
// 自動生成路由定義
if ($this->debug) {
$this->build->buildRoute($this->config('app.controller_suffix'));
}
$filename = $this->runtimePath . 'build_route.php';
if (is_file($filename)) {
include $filename;
}
}
~~~
>[danger]這里檢查是否配置了注釋自動生成路由
>如果開啟了注釋路由,則調用Build的buildRoute()解析注釋為路由
>然后將解析后的路由文件build_route.php加載到框架中
~~~
$must = !is_null($this->routeMust) ? $this->routeMust : $this->config('app.url_route_must');
return $this->route->check($path, $depr, $must, $this->config('app.route_complete_match'));
~~~
>[danger]檢查配置url_route_must是否開啟。
>如果開啟,則每個請求必須配置路由,否則默認按照模塊/控制器/操作解析
>最后調用Route的check()方法在路由中匹配url。返回匹配后的調度信息Dispatch
:-: **4 url與路由匹配過程源碼分析**
>[danger]在Route的check()開始在注冊的路由中匹配url
~~~
$domain = $this->checkDomain();
~~~
>[danger]首先檢查是否注冊了請求的域名路由信息。
>注冊的域名路由信息保存在Route的domains屬性中
>如果注冊了域名路由信息,則返回對應的域名路由信息
~~~
$url = str_replace($depr, '|', $url);
~~~
>[danger] 將url的分隔符替換為|
~~~
$result = $domain->check($this->request, $url, $depr, $completeMatch);
~~~
>[danger] 調用域名路由的Check()方法,匹配請求url
>[danger]這里的check()方法在/route/Domain.php文件中
>主要進行路由的別名和url綁定檢查 checkouteAlias() checkUrlBind()
>路由的別名和url綁定檢查由Route的getAlias()和getBind()實現
>在getAlias()和getBind()中主要讀取了Route的alias和bind屬性
>檢查是否包含了當前url和域名對應的路由信息
>[danger]最后調用Domain的父對象組路由RuleGroup的check()進行路由檢查
>在RuleGroup()首先檢查跨域請求checkCrossDomain()
>然后檢查路由規則的option參數是否有效checkOption()
>然后檢查分組路由的url是否匹配checkUrl()
~~~
if ($this->rule) {
if ($this->rule instanceof Response) {
return new ResponseDispatch($this->rule);
}
$this->parseGroupRule($this->rule);
}
~~~
>[danger]如果上述條件都符合,那么當前的路由規則就是請求url對應的路由
>然后讀取路由中注冊的調度信息rule
>如果注冊的路由調度信息rule是調度對象,則直接返回調度對象
>否則調用分組路由解析。也就是生成分組的多條路由調度信息rule
~~~
// 分組匹配后執行的行為
$this->afterMatchGroup($request);
// 獲取當前路由規則
$method = strtolower($request->method());
$rules = $this->getMethodRules($method);
if ($this->parent) {
// 合并分組參數
$this->mergeGroupOptions();
}
if (isset($this->option['complete_match'])) {
$completeMatch = $this->option['complete_match'];
}
if (!empty($this->option['merge_rule_regex'])) {
// 合并路由正則規則進行路由匹配檢查
$result = $this->checkMergeRuleRegex($request, $rules, $url, $depr, $completeMatch);
if (false !== $result) {
return $result;
}
}
// 檢查分組路由
foreach ($rules as $key => $item) {
$result = $item->check($request, $url, $depr, $completeMatch);
if (false !== $result) {
return $result;
}
}
~~~
>[danger]接下來在生成的分組路由的多條調度信息中匹配請求的url
>得到匹配的結果$result。
~~~
if ($this->auto) {
// 自動解析URL地址
$result = new UrlDispatch($this->auto . '/' . $url, ['depr' => $depr, 'auto_search' => false]);
} elseif ($this->miss && in_array($this->miss->getMethod(), ['*', $method])) {
// 未匹配所有路由的路由規則處理
$result = $this->parseRule($request, '', $this->miss->getRoute(), $url, $this->miss->getOption());
} else {
$result = false;
}
return $result;
~~~
>[danger]如果在分組路由中沒有找到url對應的路由規則
>則在auto和miss分組路由中嘗試匹配。
>最后返回匹配的結果
>也就是生成的調度信息
~~~
if (false === $result && !empty($this->cross)) {
// 檢測跨域路由
$result = $this->cross->check($this->request, $url, $depr, $completeMatch);
}
~~~
>[danger]這里返回到Route的路由檢查check()方法中
>如果沒有匹配到當前url。則檢查是否設置跨域路由
>嘗試檢查跨域路由
~~~
if (false !== $result) {
// 路由匹配
return $result;
} elseif ($must) {
// 強制路由不匹配則拋出異常
throw new RouteNotFoundException();
}
~~~
>[danger]如果得到匹配的路由調度信息則返回$result
>否則檢查是否設置強制路由,
>開啟強制路由時,匹配路由失敗則跑出路由不匹配異常
~~~
return new UrlDispatch($url, ['depr' => $depr, 'auto_search' => $this->config->get('app.controller_auto_search')]);
~~~
>[danger]如果沒有開啟強制路由,則返回url到模塊/控制器/操作的url調度對象
>得到調度對象,返回App的run()中開始記錄調度信息到request
>然后調用調度對象Dispatch的run()方法
:-: **5 調度對象的執行**
>[danger]調度對象Dispatch在think中由/route/dispatch/目錄中實現
>主要包括繼承了基礎調度對象dispath的閉包回調,控制器,模塊,跳轉,url,視圖等調度對象
>在url調度對象中調用了模塊調度對象,在模塊調度對象中最終執行了業務邏輯控制器的操作。
>操作的執行結果返回到App的run()方法中保存到$data
>然后創建對應的響應對象Response
>響應對象在/response/中實現為json,jsonp,jump,redirect,view,xml等響應對象
>其中的view也就是通常的模板響應對象