1 路由解析
在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);
}
}
首先檢查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);
獲取應用的排程資訊。這裡的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));
}
除錯模式下,儲存路由的請求資訊到日誌檔案中
// 監聽app_begin
$this->hook->listen('app_begin');
呼叫app_begin的回撥函式
$this->request->cache(
$this->config('app.request_cache'),
$this->config('app.request_cache_expire'),
$this->config('app.request_cache_except')
);
檢查否開啟了請求快取,
如果設定了快取,則嘗試讀取快取結果
// 執行排程
$data = $dispatch->run();
這裡執行排程。也就是執行請求分派到的業務邏輯,
通常是模組/控制器中的特定方法。也可以是其他形式的業務邏輯
比如 閉包函式,或者直接返回模板等。
$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);
這裡是think5.1準備新增的中介軟體功能。
$this->hook->listen('app_end', $response);
return $response;
排程執行完後,呼叫app_end的回撥函式
最後run()方法返回建立的響應物件Response
然後在入口檔案index.php中呼叫Response的send()將結果輸出到客戶端Container::get('app')->run()->send();
3 路由註冊過程原始碼分析
由上面的分析可知 路由解析的入口是App的routeCheck()
下面開始分析App的routeCheck()是如何解析請求Request得到排程資訊的
//App routeCheck()
$path = $this->request->path();
$depr = $this->config('app.pathinfo_depr');
呼叫Request的path()方法獲取當前請求的pathinfo資訊
然後讀取app.pathinfo_depr獲取pathinfo的分隔符
這裡的path就是請求的urlindex/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);
}
}
}
這裡遍歷路由目錄/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;
}
}
這裡檢查是否配置了註釋自動生成路由
如果開啟了註釋路由,則呼叫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'));
檢查配置url_route_must是否開啟。
如果開啟,則每個請求必須配置路由,否則預設按照模組/控制器/操作解析
最後呼叫Route的check()方法在路由中匹配url。返回匹配後的排程資訊Dispatch
4 url與路由匹配過程原始碼分析
在Route的check()開始在註冊的路由中匹配url
$domain = $this->checkDomain();
首先檢查是否註冊了請求的域名路由資訊。
註冊的域名路由資訊儲存在Route的domains屬性中
如果註冊了域名路由資訊,則返回對應的域名路由資訊
$url = str_replace($depr, '|', $url);
將url的分隔符替換為|
$result = $domain->check($this->request, $url, $depr, $completeMatch);
呼叫域名路由的Check()方法,匹配請求url
這裡的check()方法在/route/Domain.php檔案中
主要進行路由的別名和url繫結檢查 checkouteAlias() checkUrlBind()
路由的別名和url繫結檢查由Route的getAlias()和getBind()實現
在getAlias()和getBind()中主要讀取了Route的alias和bind屬性
檢查是否包含了當前url和域名對應的路由資訊
最後呼叫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);
}
如果上述條件都符合,那麼當前的路由規則就是請求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;
}
}
接下來在生成的分組路由的多條排程資訊中匹配請求的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;
如果在分組路由中沒有找到url對應的路由規則
則在auto和miss分組路由中嘗試匹配。
最後返回匹配的結果
也就是生成的排程資訊
if (false === $result && !empty($this->cross)) {
// 檢測跨域路由
$result = $this->cross->check($this->request, $url, $depr, $completeMatch);
}
這裡返回到Route的路由檢查check()方法中
如果沒有匹配到當前url。則檢查是否設定跨域路由
嘗試檢查跨域路由
if (false !== $result) {
// 路由匹配
return $result;
} elseif ($must) {
// 強制路由不匹配則丟擲異常
throw new RouteNotFoundException();
}
如果得到匹配的路由排程資訊則返回$result
否則檢查是否設定強制路由,
開啟強制路由時,匹配路由失敗則跑出路由不匹配異常
return new UrlDispatch($url, ['depr' => $depr, 'auto_search' => $this->config->get('app.controller_auto_search')]);
如果沒有開啟強制路由,則返回url到模組/控制器/操作的url排程物件
得到排程物件,返回App的run()中開始記錄排程資訊到request
然後呼叫排程物件Dispatch的run()方法
5 排程物件的執行
排程物件Dispatch在think中由/route/dispatch/目錄中實現
主要包括繼承了基礎排程物件dispath的閉包回撥,控制器,模組,跳轉,url,檢視等排程物件
在url排程物件中呼叫了模組排程物件,在模組排程物件中最終執行了業務邏輯控制器的操作。
操作的執行結果返回到App的run()方法中儲存到$data
然後建立對應的響應物件Response
響應物件在/response/中實現為json,jsonp,jump,redirect,view,xml等響應物件
其中的view也就是通常的模板響應物件
本作品採用《CC 協議》,轉載必須註明作者和本文連結