:-: **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也就是通常的模板响应对象