讲在后面的话:
有数人在应用TP6的过程中,被各种问题所困扰。比方:无奈依据错误信息疾速定位到问题所在,对于手册不置可否的介绍不知所云,对于共事的高级应用技巧手足无措,想扩大框架性能却不知如何下手等等。究其根本原因就是对框架的实现原理不相熟,但框架历经十四年倒退,代码量宏大,纷繁复杂,有数人想深刻其中一探到底,但也只能望洋兴叹,止步不前,苦于无人带路。为了彻底扭转这一场面,决定编写和录制一套全面、零碎、深刻介绍TP6实现原理的文字课程和视频课程以帮忙大家。
本套课程分为10个章节,别离从生命周期、申请与响应、数据库、视图、谬误和日志、验证、session和cookie、模型、路由、命令行零碎、全面、深刻介绍框架实现原理。本章节为生命周期详解,分为九大节,别离为利用创立、配置加载、服务注册、事件、加载中间件、中间件执行、路由解析、执行控制器办法、响应输入,以下本章节具体内容。
1.1 利用创立:
TP6框架采纳MVC模型组织我的项目,采纳繁多入口(所有申请都是申请同一个文件),由路由模块解析申请,获取利用、控制器、办法,接着执行办法,办法返回的内容有响应对象输入。整个生命周期的轮廓,在入口文件中和盘托出。代码如下:
<code class="php"> 1. namespace think; 3. require __DIR__ . '/../vendor/autoload.php'; 5. // 执行HTTP利用并响应 6. $http = (new App())->http; 8. // 执行利用 9. $response = $http->run(); 11. // 内容输入 12. $response->send(); 14. // 完结利用 15. $http->end($response);
首先创立 App 对象,此对象为框架中最重要的一个对象,治理后续所创立的零碎类对象,相当于对象容器。在创立的过程,会做一些初始化工作,次要是系统目录的获取,代码如下:
<code class="php"> 1. // vendortopthinkframeworksrcthinkApp.php 163行 2. public function __construct(string $rootPath = '') 3. { 4. // 框架外围目录 src/ 5. $this->thinkPath = dirname(__DIR__) . DIRECTORY_SEPARATOR; 6. // 利用根目录 比方:frshop/ 7. $this->rootPath = $rootPath ? rtrim($rootPath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR : $this->getDefaultRootPath(); 8. $this->appPath = $this->rootPath . 'app' . DIRECTORY_SEPARATOR; 9. $this->runtimePath = $this->rootPath . 'runtime' . DIRECTORY_SEPARATOR; 11. if (is_file($this->appPath . 'provider.php')) { 12. $this->bind(include $this->appPath . 'provider.php'); 13. } 15. static::setInstance($this); 17. $this->instance('app', $this); 18. $this->instance('thinkContainer', $this); 19. }
接着创立 HTTP 利用,HTTP利用的创立比拟非凡,通过获取 APP 对象不存在属性,从而触发 __get() 魔术办法,最终调用 make() 办法来创建对象,并将对象搁置于一个容器中,不便前期的获取,代码如下:
<code class="php"> 1. // vendortopthinkframeworksrcthinkContainer.php 239 行 2. public function make(string $abstract, array $vars = [], bool $newInstance = false) 3. { 4. $abstract = $this->getAlias($abstract); 6. if (isset($this->instances[$abstract]) && !$newInstance) { 7. return $this->instances[$abstract]; 8. } 10. if (isset($this->bind[$abstract]) && $this->bind[$abstract] instanceof Closure) { 11. $object = $this->invokeFunction($this->bind[$abstract], $vars); 12. } else { 13. $object = $this->invokeClass($abstract, $vars); 14. } 16. if (!$newInstance) { 17. $this->instances[$abstract] = $object; 18. } 20. return $object; 21. }
紧接着执行 HTTP 利用类的 run() 办法启动一个 HTTP 利用。
1.2 配置加载:
其实,对于任何一个软件而言,利用执行的第一步都是加载配置,当然TP6也不例外。TP6的配置加载不局限于配置文件的加载,其中包含环境变量加载、助手函数文件加载、配置文件加载、事件文件加载、服务文件加载等等,代码如下:
<code class="php"> 1. // vendortopthinkframeworksrcthinkApp.php 493 行 2. protected function load(): void 3. { 4. $appPath = $this->getAppPath(); 6. if (is_file($appPath . 'common.php')) { 7. include_once $appPath . 'common.php'; 8. } 10. include_once $this->thinkPath . 'helper.php'; 12. $configPath = $this->getConfigPath(); 14. $files = []; 16. if (is_dir($configPath)) { 17. $files = glob($configPath . '*' . $this->configExt); 18. } 20. foreach ($files as $file) { 21. $this->config->load($file, pathinfo($file, PATHINFO_FILENAME)); 22. } 24. if (is_file($appPath . 'event.php')) { 25. $this->loadEvent(include $appPath . 'event.php'); 26. } 28. if (is_file($appPath . 'service.php')) { 29. $services = include $appPath . 'service.php'; 30. foreach ($services as $service) { 31. $this->register($service); 32. } 33. } 34. }
框架配置信息皆由 Config 对象接管,所有配置信息的设置和获取都通过 Config 对象所提供的的接口设置和获取,其中重要的接口有 has() 、set() 、 get()。
1.3 服务注册:
什么是服务?服务就是一个插件,用来扩大内核的性能,分为自定义服务和内置服务,自定义服务可由命令行的 make 命令创立,在通过配置文件配置,就可被框架加载,配置形式如下:
<code class="php"> 1. // appservice.php 3. use appAppService; 5. // 零碎服务定义文件 6. // 服务在实现全局初始化之后执行 7. return [ 8. AppService::class, 9. ];
注册形式如下:
1. // vendortopthinkframeworksrcthinkApp.php 519行 2. if (is_file($appPath . 'service.php')) { 3. $services = include $appPath . 'service.php'; 4. foreach ($services as $service) { 5. $this->register($service); 6. } 7. }
零碎服务注册由 RegisterService 类实现,代码如下:
<code class="php"> 1. // vendortopthinkframeworksrcthinkinitializerRegisterService.php 2. /** 3. * 注册零碎服务 4. */ 5. class RegisterService 6. { 8. protected $services = [ 9. PaginatorService::class, 10. ValidateService::class, 11. ModelService::class, 12. ]; 14. public function init(App $app) 15. { 16. $file = $app->getRootPath() . 'vendor/services.php'; 18. $services = $this->services; 20. if (is_file($file)) { 21. $services = array_merge($services, include $file); 22. } 24. foreach ($services as $service) { 25. if (class_exists($service)) { 26. $app->register($service); 27. } 28. } 29. } 30. }
1.4 事件:
什么是事件?在某个地点,某个工夫产生的一件事。纯正探讨事件自身并没有多大意义的,而是事件产生后咱们为此做些什么事才是有意义的,这就是事件绑定操作,也就是所谓的事件机制。事件机制能灵便扩大框架的性能,整个事件机制的实现须要三步,一、创立操作(或者叫事件监听),二、注册事件监听,三、触发事件,代码如下:
创立事件监听:
<code class="php"> 1. // 事件类能够通过命令行疾速生成 2. php think make:event AppInit 4. // 批改 event.php 5. return [ 6. 'bind' => [ 7. ], 9. 'listen' => [ 10. 'AppInit' => ['applistenerAppInit'], 11. 'HttpRun' => [], 12. 'HttpEnd' => [], 13. 'LogLevel' => [], 14. 'LogWrite' => [], 15. ], 17. 'subscribe' => [ 18. ], 19. ];
注册事件监听(此步骤由零碎实现)
<code class="php"> 1. // vendortopthinkframeworksrcthinkEvent.php 59 2. public function listenEvents(array $events) 3. { 4. foreach ($events as $event => $listeners) { 5. if (isset($this->bind[$event])) { 6. $event = $this->bind[$event]; 7. } 9. $this->listener[$event] = array_merge($this->listener[$event] ?? [], $listeners); 10. } 12. return $this; 13. }
触发事件(此步骤由程序员实现)
<code class="php"> 1. // 触发事件 2. $app->event->trigger(AppInit::class);
1.5 加载中间件:
什么是中间件?顾名思义,就是位于两头的组件,也就是位于生命周期两头的组件,作用就是扩大内核性能。中间件对于框架非常重要,好几个重要的工性能都是由中间件实现,像 session、申请缓存等等。咱们也能够通过中间件来做权限的验证,不论是本人定义的中间件还是内置的中间件,他们的加载都依赖于配置文件,只有定义在配置文件中的中间件能力加载,代码如下:
<code class="php"> 1. // 全局中间件定义文件 appmiddleware.php 2. return [ 3. // 全局申请缓存 4. thinkmiddlewareCheckRequestCache::class, 5. // 多语言加载 6. thinkmiddlewareLoadLangPack::class, 7. // Session初始化 8. thinkmiddlewareSessionInit::class 9. ];
中间价加载,代码如下:
<code class="php"> 1. // 加载全局中间件 vendortopthinkframeworksrcthinkHttp.php 192 行 2. $this->loadMiddleware(); 4. // loadMiddleware() vendortopthinkframeworksrcthinkHttp.php 216行 5. protected function loadMiddleware(): void 6. { 7. if (is_file($this->app->getBasePath() . 'middleware.php')) { 8. $this->app->middleware->import(include $this->app->getBasePath() . 'middleware.php'); 9. } 10. } 12. // import() 办法实现 vendortopthinkframeworksrcthinkMiddleware.php 51行 13. public function import(array $middlewares = [], string $type = 'global'): void 14. { 15. foreach ($middlewares as $middleware) { 16. $this->add($middleware, $type); 17. } 18. }
中间价由 Middleware 类接管,中间价存储在 Middleware 对象的 $queue 属性中。
1.6 执行中间件:
中间件的执行应该是整个框架中最外围的中央,也是最难了解的中央,实现的十分奇妙,用文字难以表白分明,大家能够观看视频教程,这里把代码贴出来。
<code class="php"> 1. // 执行应用程序 vendortopthinkframeworksrcthinkHttp.php 187 行 2. protected function runWithRequest(Request $request) 3. { 4. $this->initialize(); 6. // 加载全局中间件 7. $this->loadMiddleware(); 9. // 监听HttpRun 10. $this->app->event->trigger(HttpRun::class); 12. // 这段代码涵盖生命周期的90%,其中包含中间件的执行 13. return $this->app->middleware->pipeline() 14. ->send($request) 15. ->then(function ($request) { 16. return $this->dispatchToRoute($request); 17. }); 18. }
<code class="php"> 1. // 调度管道 vendortopthinkframeworksrcthinkMiddleware.php 133 行 2. public function pipeline(string $type = 'global') 3. { 4. return (new Pipeline()) 5. ->through(array_map(function ($middleware) { 6. // 这里中间件的执行逻辑,但中间件并没有在这里执行,只是一个回调函数而已 7. return function ($request, $next) use ($middleware) { 8. [$call, $params] = $middleware; 9. if (is_array($call) && is_string($call[0])) { 10. $call = [$this->app->make($call[0]), $call[1]]; 11. } 12. $response = call_user_func($call, $request, $next, ...$params); 14. if (!$response instanceof Response) { 15. throw new LogicException('The middleware must return Response instance'); 16. } 17. return $response; 18. }; 19. }, $this->sortMiddleware($this->queue[$type] ?? []))) 20. ->whenException([$this, 'handleException']); 21. }
<code class="php"> 1. // 执行 vendortopthinkframeworksrcthinkPipeline.php 52 行 2. // 这段代码十分的拗口,其难点在于 array_reduce 这个函数,吃透这个函数,就能了解这段代码 3. public function then(Closure $destination) 4. { 5. $pipeline = array_reduce( 6. array_reverse($this->pipes), 7. $this->carry(), 8. function ($passable) use ($destination) { 9. try { 10. return $destination($passable); 11. } catch (Throwable | Exception $e) { 12. return $this->handleException($passable, $e); 13. } 14. }); 16. return $pipeline($this->passable); 17. }
1.7 路由解析
什么是路由?说穿了路由就是解决从哪里来要到哪里去的问题,框架的最小执行单元是办法,也就是所有的申请最终的落脚点都在办法,然而咱们也晓得框架是繁多入口,所有的申请都是申请同一个文件,那怎么去定位办法呢,这个事由路由实现。路由就是通过解析申请信息,剖析出利用->控制器->办法,而后调用办法,并且将办法返回的内容交给响应。外围代码如下:
路由调度
<code class="php"> 1. // 通过此办法 将路由解析 从 http 对象转给 route 对象 2. // vendortopthinkframeworksrcthinkHttp.php 204 行 3. protected function dispatchToRoute($request) 4. { 5. $withRoute = $this->app->config->get('app.with_route', true) ? function () { 6. $this->loadRoutes(); 7. } : null; 9. return $this->app->route->dispatch($request, $withRoute); 10. } 13. // 路由调度 这是路由解析的外围代码 14. // vendortopthinkframeworksrcthinkRoute.php 739 行 15. public function dispatch(Request $request, $withRoute = true) 16. { 17. $this->request = $request; 18. $this->host = $this->request->host(true); 19. $this->init(); 21. if ($withRoute) { 22. //加载路由 23. if ($withRoute instanceof Closure) { 24. $withRoute(); 25. } 26. $dispatch = $this->check(); 27. } else { 28. $dispatch = $this->url($this->path()); 29. } 31. $dispatch->init($this->app); 33. return $this->app->middleware->pipeline('route') 34. ->send($request) 35. ->then(function () use ($dispatch) { 36. return $dispatch->run(); 37. }); 38. }
解析 url 获取控制器和办法
<code class="php"> 1. // 解析 url vendortopthinkframeworksrcthinkroutedispatchUrl.php 2. protected function parseUrl(string $url): array 3. { 4. $depr = $this->rule->config('pathinfo_depr'); 5. $bind = $this->rule->getRouter()->getDomainBind(); 7. if ($bind && preg_match('/^[a-z]/is', $bind)) { 8. $bind = str_replace('/', $depr, $bind); 9. // 如果有域名绑定 10. $url = $bind . ('.' != substr($bind, -1) ? $depr : '') . ltrim($url, $depr); 11. } 13. $path = $this->rule->parseUrlPath($url); 14. if (empty($path)) { 15. return [null, null]; 16. } 18. // 解析控制器 19. $controller = !empty($path) ? array_shift($path) : null; 21. if ($controller && !preg_match('/^[A-Za-z0-9][w|.]*$/', $controller)) { 22. throw new HttpException(404, 'controller not exists:' . $controller); 23. } 25. // 解析操作 26. $action = !empty($path) ? array_shift($path) : null; 27. $var = []; 29. // 解析额定参数 30. if ($path) { 31. preg_replace_callback('/(w+)|([^|]+)/', function ($match) use (&$var) { 32. $var[$match[1]] = strip_tags($match[2]); 33. }, implode('|', $path)); 34. } 36. $panDomain = $this->request->panDomain(); 37. if ($panDomain && $key = array_search('*', $var)) { 38. // 泛域名赋值 39. $var[$key] = $panDomain; 40. } 42. // 设置以后申请的参数 43. $this->param = $var; 45. // 封装路由 46. $route = [$controller, $action]; 48. if ($this->hasDefinedRoute($route)) { 49. throw new HttpException(404, 'invalid request:' . str_replace('|', $depr, $url)); 50. } 52. return $route; 53. }
1.8 执行控制器办法:
通过 url 解析之后,咱们就能够拿到控制器和办法(如果是多利用,那就是利用->控制器->办法,url 的解析定义在多利用包外面),接下来就是执行办法。代码如下:
<code class="php"> 1. // 执行路由调度 vendortopthinkframeworksrcthinkrouteDispatch.php 2. public function run(): Response 3. { 4. if ($this->rule instanceof RuleItem && $this->request->method() == 'OPTIONS' && $this->rule->isAutoOptions()) { 5. $rules = $this->rule->getRouter()->getRule($this->rule->getRule()); 6. $allow = []; 7. foreach ($rules as $item) { 8. $allow[] = strtoupper($item->getMethod()); 9. } 11. return Response::create('', 'html', 204)->header(['Allow' => implode(', ', $allow)]); 12. } 14. // 这里是执行控制器办法返回的数据 15. $data = $this->exec(); 16. return $this->autoResponse($data); 17. }
执行办法
1. // 执行办法 2. // vendortopthinkframeworksrcthinkroutedispatchController.php 70行 3. public function exec() 4. { 5. try { 6. // 实例化控制器 7. $instance = $this->controller($this->controller); 8. } catch (ClassNotFoundException $e) { 9. throw new HttpException(404, 'controller not exists:' . $e->getClass()); 10. } 12. // 注册控制器中间件 13. $this->registerControllerMiddleware($instance); 15. return $this->app->middleware->pipeline('controller') 16. ->send($this->request) 17. ->then(function () use ($instance) { 18. // 获取以后操作名 19. $suffix = $this->rule->config('action_suffix'); 20. $action = $this->actionName . $suffix; 22. if (is_callable([$instance, $action])) { 23. $vars = $this->request->param(); 24. try { 25. $reflect = new ReflectionMethod($instance, $action); 26. // 严格获取以后操作方法名 27. $actionName = $reflect->getName(); 28. if ($suffix) { 29. $actionName = substr($actionName, 0, -strlen($suffix)); 30. } 32. $this->request->setAction($actionName); 33. } catch (ReflectionException $e) { 34. $reflect = new ReflectionMethod($instance, '__call'); 35. $vars = [$action, $vars]; 36. $this->request->setAction($action); 37. } 38. } else { 39. // 操作不存在 40. throw new HttpException(404, 'method not exists:' . get_class($instance) . '->' . $action . '()'); 41. } 43. // 通过反射执行 控制器办法 44. $data = $this->app->invokeReflectMethod($instance, $reflect, $vars); 46. return $this->autoResponse($data); 47. }); 48. }
1.9 响应输入:
控制器办法执行返回的内容由响应对象接管,响应对象通过一系列解决之后将内容进行输入,输入之后响应对象将会敞开申请,但此时利用并没有完结,而是做一些收尾工作,比方 session 写入、日志写入,接下来看看具体代码:
接管内容
<code class="php"> 1. // 接管内容 依据内容创立不同的响应对象 2. protected function autoResponse($data): Response 3. { 4. if ($data instanceof Response) { 5. $response = $data; 6. } elseif (!is_null($data)) { 7. // 默认自动识别响应输入类型 8. $type = $this->request->isJson() ? 'json' : 'html'; 9. $response = Response::create($data, $type); 10. } else { 11. $data = ob_get_clean(); 13. $content = false === $data ? '' : $data; 14. $status = '' === $content && $this->request->isJson() ? 204 : 200; 15. $response = Response::create($content, 'html', $status); 16. } 18. return $response; 19. }
输入
<code class="php"> 1. // 输入内容 vendortopthinkframeworksrcthinkResponse.php 128 2. public function send(): void 3. { 4. // 解决输入数据 5. $data = $this->getContent(); 7. if (!headers_sent() && !empty($this->header)) { 8. // 发送状态码 9. http_response_code($this->code); 10. // 发送头部信息 11. foreach ($this->header as $name => $val) { 12. header($name . (!is_null($val) ? ':' . $val : '')); 13. } 14. } 15. if ($this->cookie) { 16. $this->cookie->save(); 17. } 19. $this->sendData($data); 21. // 敞开申请 22. if (function_exists('fastcgi_finish_request')) { 23. // 进步页面响应 24. fastcgi_finish_request(); 25. } 26. }
收尾
<code class="php"> 1. // 收尾 vendortopthinkframeworksrcthinkHttp.php 271 行 2. public function end(Response $response): void 3. { 4. $this->app->event->trigger(HttpEnd::class, $response); 6. //执行中间件 7. $this->app->middleware->end($response); 9. // 写入日志 10. $this->app->log->save(); 11. }
整个生命周期的介绍到这里就全副完结了,感谢您的浏览。
读完此文,如果感觉意犹未尽,看移步观看视频教程,并且能够和作者一对一交换。
地址:https://edu.csdn.net/course/detail/28045