🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
:-: **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也就是通常的模板响应对象