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

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐