TP6实现原理分析系列(一):生命周期
码农天地 -讲在前面的话:
无数人在使用TP6的过程中,被各种问题所困扰。比如:无法根据错误信息快速定位到问题所在,对于手册模棱两可的介绍不知所云,对于同事的高级使用技巧不知所措,想扩展框架功能却不知如何下手等等。究其根本原因就是对框架的实现原理不熟悉,但框架历经十四年发展,代码量庞大,纷繁复杂,无数人想深入其中一探究竟,但也只能望洋兴叹,止步不前,苦于无人领路。为了彻底扭转这一局面,决定编写和录制一套全面、系统、深入介绍TP6实现原理的文字课程和视频课程以帮助大家。
本套课程分为10个章节,分别从生命周期、请求与响应、数据库、视图、错误和日志、验证、session和cookie、模型、路由、命令行系统、全面、深入介绍框架实现原理。本章节为生命周期详解,分为九小节,分别为应用创建、配置加载、服务注册、事件、加载中间件、中间件执行、路由解析、执行控制器方法、响应输出,以下本章节详细内容。
1.1 应用创建:TP6框架采用MVC模型组织项目,采用单一入口(所有请求都是请求同一个文件),由路由模块解析请求,获取应用、控制器、方法,接着执行方法,方法返回的内容有响应对象输出。整个生命周期的轮廓,在入口文件中一览无余。代码如下:
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 对象,此对象为框架中最重要的一个对象,管理后续所创建的系统类对象,相当于对象容器。在创建的过程,会做一些初始化工作,主要是系统目录的获取,代码如下:
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() 方法来创建对象,并将对象放置于一个容器中,方便后期的获取,代码如下:
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的配置加载不局限于配置文件的加载,其中包括环境变量加载、助手函数文件加载、配置文件加载、事件文件加载、服务文件加载等等,代码如下:
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 命令创建,在通过配置文件配置,就可被框架加载,配置方式如下:
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 类完成,代码如下:
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 事件:什么是事件?在某个地点,某个时间发生的一件事。纯粹讨论事件本身并没有多大意义的,而是事件发生后我们为此做些什么事才是有意义的,这就是事件绑定操作,也就是所谓的事件机制。事件机制能灵活扩展框架的功能,整个事件机制的完成需要三步,一、创建操作(或者叫事件监听),二、注册事件监听,三、触发事件,代码如下:
创建事件监听:
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. ];
注册事件监听(此步骤由系统完成)
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. }
触发事件(此步骤由程序员完成)
1. // 触发事件
2. $app->event->trigger(AppInit::class);
1.5 加载中间件:什么是中间件?顾名思义,就是位于中间的组件,也就是位于生命周期中间的组件,作用就是扩展内核功能。中间件对于框架十分重要,好几个重要的工功能都是由中间件完成,像 session、请求缓存等等。我们也可以通过中间件来做权限的验证,不管是自己定义的中间件还是内置的中间件,他们的加载都依赖于配置文件,只有定义在配置文件中的中间件才能加载,代码如下:
1. // 全局中间件定义文件 appmiddleware.php
2. return [
3. // 全局请求缓存
4. thinkmiddlewareCheckRequestCache::class,
5. // 多语言加载
6. thinkmiddlewareLoadLangPack::class,
7. // Session初始化
8. thinkmiddlewareSessionInit::class
9. ];
中间价加载,代码如下:
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 执行中间件:中间件的执行应该是整个框架中最核心的地方,也是最难理解的地方,实现的非常巧妙,用文字难以表达清楚,大家可以观看视频教程,这里把代码贴出来。
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. }
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. }
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 路由解析什么是路由?说穿了路由就是解决从哪里来要到哪里去的问题,框架的最小执行单元是方法,也就是所有的请求最终的落脚点都在方法,但是我们也知道框架是单一入口,所有的请求都是请求同一个文件,那怎么去定位方法呢,这个事由路由完成。路由就是通过解析请求信息,分析出应用->控制器->方法,然后调用方法,并且将方法返回的内容交给响应。核心代码如下:
路由调度
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 获取控制器和方法
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 的解析定义在多应用包里面),接下来就是执行方法。代码如下:
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 写入、日志写入,接下来看看详细代码:
接管内容
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. }
输出
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. }
收尾
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
php介绍
PHP即“超文本预处理器”,是一种通用开源脚本语言。PHP是在服务器端执行的脚本语言,与C语言类似,是常用的网站编程语言。PHP独特的语法混合了C、Java、Perl以及 PHP 自创的语法。利于学习,使用广泛,主要适用于Web开发领域。
Tags 标签
phpthinkphp6内核扩展阅读
隐藏apache版本信息
2018-09-30 10:56:15 []CentOS 6.5安装php5.6
2018-09-30 11:36:53 []PHP版ZIP压缩解压类库
2018-12-22 13:11:00 []CentOS7.2安装 PHP7.3.4 操作详细教程
2020-06-28 19:09:43 []PHP 设置脚本超时时间、PHP脚本内存限制设置
2020-06-28 19:09:43 []PHP 函数filesize获取文件大小错误,一直不变
2020-06-28 19:09:43 []Linux php: command not found
2020-02-05 01:30:13 []php 缓冲区 buffer 原理
2020-06-28 19:09:43 []PHP中三种设置脚本最大执行时间的方法
2020-06-28 19:17:34 []加个好友,技术交流
