乐趣区

关于php: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

退出移动版