💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
1. 入口文件先实例化容器,然后再通过容器去获取到Http对象 (Web管理类),然后执行Http对象中的run方法。 2. 方法内会创建一个Request对象,然后将对象绑定到容器内。然后再到runWithRequest方法,执行应用程序 3. runWithRequest方法内会初始化当前应用,简单来说就比如加载一下语言包,加载一下应用文件。common.php公共函数文件。helper.php助手函数文件、.env环境变量、运行开始的时间、设置时区、加载中间件等等。然后到dispatchToRoute方法,传入当前的Request请求对象。 4. dispatchToRoute方法,这里我姑且称为路由初始化方法。这里主要就是检测配置文件,是否开启了路由。如果开启了路由。就加载路由文件。并且设置一个匿名函数。只有在调用的时候才会加载设置的路由。接着会通过容器获取route类的实例。并且传入当前Request对象和路由配置的匿名函数并执行里面的dispatch方法 5. dispatch方法,主要就是路由初始化。判断路由是否配置。如果没有配置就直接执行默认控制器和默认方法。如果有的话。就加载一下路由配置,再执行check方法,通过check方法。去检测是什么路由。然后调用url方法传入当前的url地址 6. url方法执行并且会返回一个Url基础类 7. 这里我称之为url且切割类吧。他主要的作用就是将传入的Request对象,rule路由规则对象,以及当前的url地址。把url地址解析。这里又去调用了一个parseUrl方法。 8. 这个方法我就不多介绍了。它主要的作用就是分割出来要执行的control和function。 9. 到这里就结束了url的部分。又回到了第五部。去调用url类中的init方法(路由后置操作。中间件).然后通过middleware中的then。到最后执行一个上一步返回的Url类中的run方法 10. run这里主要就是最通过exec再去获取控制器(controller)和对应的方法(function)的结果。然后创建一个Response对象。最后返回 11. 最后回到了入口文件run方法下面还有一个send方法。源码就不贴了。他的作用就是输出 12. 入口文件最后一行。调用了一下Http类的end方法。简单说就是挂个HttpEnd中间件。然后执行中间件。最后再记录一下日志。 ## 0x01 源码解析 ### 1、public/index.php ~~~ // [ 应用入口文件 ] //定义根命名空间 namespace think; //引入composer require __DIR__ . '/../vendor/autoload.php'; //通过Ioc容器将HTTP类实例出来 // 执行HTTP应用并响应 $http = (new App())->http; //执行HTTP类中的run类方法 并返回一个response对象 $response = $http->run(); //执行response对象中的send类方法 该方法是处理并输出http状态码以及页面内容 $response->send(); //执行response对象中的send方法 $http->end($response); ~~~ ### 2、通过\\Think\\App容器获取到Http对象,然后再执行Http对象中的run方法 ~~~ /** * 执行应用程序 * @access public * @param Request|null $request * @return Response */ public function run(Request $request = null): Response { //判断是否传入Request对象,如果没有则创建 $request = $request ?? $this->app->make('request', [], true); //将Request绑定到App容器内 $this->app->instance('request', $request); try { //runWithRequest方法 作用是执行应用程序 $response = $this->runWithRequest($request); } catch (Throwable $e) { //如果捕捉到Throwable异常 则执行reportException方法 //调用Handle::class实例中的report方法。收集异常信息 $this->reportException($e); //通过调用Handle::class实例中的render方法 //获取异常信息输出流 $response = $this->renderException($request, $e); } //return 内容 return $response; } ~~~ ### 3、\\Think\\Http->runWithRequest() 初始化应用程序,并执行 ~~~ protected function runWithRequest(Request $request) { //初始化应用程序 $this->initialize(); // 加载全局中间件 $this->loadMiddleware(); // 设置开启事件机制 $this->app->event->withEvent($this->app->config->get('app.with_event', true)); // 监听HttpRun $this->app->event->trigger(HttpRun::class); //这里重点 return $this->app->middleware->pipeline() ->send($request) ->then(function ($request) { //通过dispatchToRoute方法加载路由 return $this->dispatchToRoute($request); }); } ~~~ ### 4、\\Think\\Http->dispatchToRoute()   ~~~ protected function dispatchToRoute($request) { //通过容器控制反转快速获取config类实例 //并获取配置文件中的with_route 判断是否加载路由 //如果加载则返回一个匿名函数。里面是路由文件内的设置 $withRoute = $this->app->config->get('app.with_route', true) ? function () { $this->loadRoutes(); } : null; //执行\Think\Route类中的dispatch方法,并且将获取到的路由 //文件以及当前的Request实例传入到路由中,然后进行路由调度 return $this->app->route->dispatch($request, $withRoute); } ~~~ ### 5、\\Think\\Route->dispatch()  路由 ~~~ public function dispatch(Request $request, $withRoute = null) { //设置传入的Request对象到当前对象的属性上 $this->request = $request; //同上 这个是设置host $this->host = $this->request->host(true); //执行Route init 方法 初始化 $this->init(); //判断的withRoute是否为真 if ($withRoute) { //执行传入过来的匿名函数,加载路由 $withRoute(); //官方注释的是检测url路由,我这里姑且认为是路由分发吧 //check返回的是think\route\Dispatch 路由调度基础类对象 $dispatch = $this->check(); } else { //调用think\route\dispatch\Url类 //将当前的url地址传入进去,进行默认的url解析 $dispatch = $this->url($this->path()); } //执行Dispatch对象中的init方法 //这里用于绑定控制器和方法以及路由后置操作,例如:中间件、绑定模型数据 $dispatch->init($this->app); //执行路由调度。并返回一个Response对象 return $this->app->middleware->pipeline('route') ->send($request) ->then(function () use ($dispatch) { return $dispatch->run(); }); } ~~~ ### 6、\\Think\\Route->init()  初始化 ~~~ protected function init() { //合并默认配置以及读取到的route.php配置 $this->config = array_merge($this->config, $this->app->config->get('route')); //判断路由中间件是否存储,如果存在则调用middleware类中的import方法 //注册route中间件 if (!empty($this->config['middleware'])) { $this->app->middleware->import($this->config['middleware'], 'route'); } //是否延迟解析 $this->lazy($this->config['url_lazy_route']); //读取到的 是否合并路由配置项赋值到类变量mergeRuleRegex中 $this->mergeRuleRegex = $this->config['route_rule_merge']; //获取配置:是否删除url最后的斜线 $this->removeSlash = $this->config['remove_slash']; //是否去除url最后的斜线 $this->group->removeSlash($this->removeSlash); } ~~~ ### 7、Think\\Route\\Dispatch->init() ``` `public``function``init(App``$app``)` `{` `$this``->app =``$app``;` `// 执行路由后置操作` `$this``->doRouteAfter();` `}` ``` ### 8、Think\\Route\\Dispatch->run() ``` `public``function``run(): Response` `{` `//判断$this->rule路由规则是否为RuleItem类的实例` `//判断当前请求方法,是不是OPTIONS以及` `//判断当前路由规则是否为自动注册的OPTIONS路由` `if``(``$this``->rule``instanceof``RuleItem &&``$this``->request->method() ==``'OPTIONS'``&&``$this``->rule->isAutoOptions()) {` `//获取当前的路由列表` `$rules``=``$this``->rule->getRouter()->getRule(``$this``->rule->getRule());` `$allow``= [];` `foreach``(``$rules``as``$item``) {` `//这里是循环把所有路由全部转成大写` `$allow``[] =``strtoupper``(``$item``->getMethod());` `}` `//创建并返回一个Response对象,调用create静态方法` `return``Response::create(``''``,``'html'``, 204)->header([``'Allow'``=> implode(``', '``,``$allow``)]);` `}` `//如果上面的不匹配则调用当前Dispatch类中的exec方法` `//实例化控制器以及方法` `$data``=``$this``->``exec``();` `//最后动态的返回一个Response对象。xml、json等等` `return``$this``->autoResponse(``$data``);` `}` ``` ### 9、\\Think\\Route->check()  ``` `public``function``check(): Dispatch` `{` `//转换PATH_INFO分隔符,拼接url` `$url``=``str_replace``(``$this``->config[``'pathinfo_depr'``],``'|'``,``$this``->path());` `//获取是否完全匹配 配置项` `$completeMatch``=``$this``->config[``'route_complete_match'``];` `//调用checkDomain检测是否为域名路由如果不是则返回false` `//如果是域名路由,则返回一个Domain对象。并且执行对象中的check方法` `//并把当前的Request请求对象以及url地址和是否完全匹配路由项传入进去` `$result``=``$this``->checkDomain()->check(``$this``->request,``$url``,``$completeMatch``);` `//判断result是否为false 也就是不是域名路由` `//再判断是否为跨域路由` `if``(false ===``$result``&& !``empty``(``$this``->cross)) {` `// 如果是跨域路由,就将当前的Request请求对象以及url地址和是否完全匹配路由项传入进去` `$result``=``$this``->cross->check(``$this``->request,``$url``,``$completeMatch``);` `}` `//如果是域名路由` `if``(false !==``$result``) {` `//直接返回 $result变量 变量内存储着 RuleGroup对象实例` `//路由规则` `return``$result``;` `}``elseif``(``$this``->config[``'url_route_must'``]) {` `//判断是否启用了强制路由,如果启用了强制路由` `//然后域名路由也匹配不上。就触发一个路由` `//找不到的异常类` `throw``new``RouteNotFoundException();` `}` `//以上都不匹配,则调用url函数,传入当前的url地址` `//返回一个Url类实例` `return``$this``->url(``$url``);` `}` ``` ### 10、\\Think\\Route->url() ``` `public``function``url(string``$url``): UrlDispatch` `{` `return``new``UrlDispatch(``$this``->request,``$this``->group,``$url``);` `}` ``` ### 11、\\Think\\Route\\dispatch\\Url  url切割类(自己给的称呼) ``` `//构造函数` `public``function``__construct(Request``$request``, Rule``$rule``,``$dispatch``,``array``$param``= [], int``$code``= null)` `{` `//获取传入来的Request对象,存储到类成员变量内` `$this``->request =``$request``;` `//获取到路由列表,也放到类成员变量内` `$this``->rule    =``$rule``;` `// 调用类中的parseUrl方法 解析URL规则` `$dispatch``=``$this``->parseUrl(``$dispatch``);` `//调用父类构造函数` `parent::__construct(``$request``,``$rule``,``$dispatch``,``$this``->param,``$code``);` `}` `protected``function``parseUrl(string``$url``):``array` `{` `//获取到分隔符` `$depr``=``$this``->rule->config(``'pathinfo_depr'``);` `//获取当前域名` `$bind``=``$this``->rule->getRouter()->getDomainBind();` `//如果域名不为空,并且正则匹配的到` `if``(``$bind``&& preg_match(``'/^[a-z]/is'``,``$bind``)) {` `//切割url,换成配置项中的PATH_INFO分隔符` `$bind``=``str_replace``(``'/'``,``$depr``,``$bind``);` `// 如果有域名绑定` `$url``=``$bind``. (``'.'``!=``substr``(``$bind``, -1) ?``$depr``:``''``) . ltrim(``$url``,``$depr``);` `}` `//调用rule类中的parseUrlPath方法,切割pathinfo参数` `//如果url中有参数 返回一个demo吧  ['Index','Demo']` `//第一个为控制器、第二个为方法` `$path``=``$this``->rule->parseUrlPath(``$url``);` `//如果切割的pathinfo为空,则直接返回一个[null,null] 这样的一个空数组` `if``(``empty``(``$path``)) {` `return``[null, null];` `}` `//获取到第一个下标  控制器` `$controller``= !``empty``(``$path``) ?``array_shift``(``$path``) : null;` `//正则匹配,如果匹配不到。就弹出一个HttpException异常` `if``(``$controller``&& !preg_match(``'/^[A-Za-z0-9][\w|\.]*$/'``,``$controller``)) {` `throw``new``HttpException(404,``'controller not exists:'``.``$controller``);` `}` `//获取到第二个下标  方法 function` `// 解析操作` `$action``= !``empty``(``$path``) ?``array_shift``(``$path``) : null;` `$var`   `= [];` `// 解析额外参数` `//类似于  /index.php/Index/Users/Pascc` `//这样就会返回一个 三个下标的数组` `if``(``$path``) {` `//这里将多余的下标,放到var变量内` `preg_replace_callback(``'/(\w+)\|([^\|]+)/'``,``function``(``$match``)``use``(&``$var``) {` `$var``[``$match``[1]] =``strip_tags``(``$match``[2]);` `}, implode(``'|'``,``$path``));` `}` `//获取到泛域名 再判断其中是否有*符号` `$panDomain``=``$this``->request->panDomain();` `if``(``$panDomain``&&``$key``=``array_search``(``'*'``,``$var``)) {` `// 泛域名赋值` `$var``[``$key``] =``$panDomain``;` `}` `// 设置当前请求的参数` `$this``->param =``$var``;` `// 封装路由` `$route``= [``$controller``,``$action``];` `//判断路由,是否存在 不存在则弹出未找到路由` `if``(``$this``->hasDefinedRoute(``$route``)) {` `throw``new``HttpException(404,``'invalid request:'``.``str_replace``(``'|'``,``$depr``,``$url``));` `}` `//返回路由` `return``$route``;` ``` ### 12、\\Think\\Route\\dispatch\\controller->init()  绑定控制器和方法 ~~~ public function init(App $app) { parent::init($app); $result = $this->dispatch; if (is_string($result)) { $result = explode('/', $result); } // 获取控制器名 $controller = strip_tags($result[0] ?: $this->rule->config('default_controller')); if (strpos($controller, '.')) { $pos = strrpos($controller, '.'); $this->controller = substr($controller, 0, $pos) . '.' . Str::studly(substr($controller, $pos + 1)); } else { $this->controller = Str::studly($controller); } // 获取操作名 $this->actionName = strip_tags($result[1] ?: $this->rule->config('default_action')); // 设置当前请求的控制器、操作 $this->request ->setController($this->controller) ->setAction($this->actionName); } ~~~ ### 13、接下来流程就是回到第5步 #### 最终会返回一个Response对象。流程又回到了第2步过程 ### 14、然后会在入口文件中 ``` `namespace``think;` `//引入composer` `require``__DIR__ .``'/../vendor/autoload.php'``;` `$http``= (``new``App())->http;` `$response``=``$http``->run();` `//执行返回的Response对象中的send方法` `//执行response对象中的send类方法  该方法是处理并输出http状态码以及页面内容` `$response``->send();` `//执行Http对象中的send方法` `$http``->``end``(``$response``);` `//最终输出到页面上我` ```