关于php:TP6实现原理分析系列二请求与响应

48次阅读

共计 10939 个字符,预计需要花费 28 分钟才能阅读完成。

讲在后面的话:

申请与响应是框架生命周期中两个重要的环节,是框架的收尾两端。申请负责接管客户端申请信息,并对外提供大量接口,获取更精细化数据。响应负责将业务逻辑执行后的后果以各种模式输入。本章节将从九个方面具体介绍申请与响应的实现细节,九大节别离是:申请信息、申请变量、申请类型、头信息、申请缓存、响应类型、响应输入、响应参数、重定向。接下来是具体内容:

2.1 申请信息:

什么是申请信息?申请信息是指客户端申请服务端,发送过去的所有信息,这些信息包含申请协定、域名、端口、申请办法、申请参数等等,对于这些信息 PHP 语言将它们存储在一些超全局数组中,所谓的申请对象接管申请信息就是接管这些超全局数组,请看具体代码:



1.  // 这是 Request 类的初始化办法,接管了除 $_SESSION 之外的超全局数据
    
2.  // vendortopthinkframeworksrcthinkRequest.php
    
3.  public static function __make(App $app)
    
4.  {5.          $request = new static();
    

7.          if (function_exists('apache_request_headers') && $result = apache_request_headers()) {
    
8.              $header = $result;
    
9.          } else {10.              $header = [];
    
11.              $server = $_SERVER;
    
12.              foreach ($server as $key => $val) {13.                  if (0 === strpos($key, 'HTTP_')) {14.                      $key          = str_replace('_', '-', strtolower(substr($key, 5)));
    
15.                      $header[$key] = $val;
    
16.                  }
    
17.              }
    
18.              if (isset($server['CONTENT_TYPE'])) {19.                  $header['content-type'] = $server['CONTENT_TYPE'];
    
20.              }
    
21.              if (isset($server['CONTENT_LENGTH'])) {22.                  $header['content-length'] = $server['CONTENT_LENGTH'];
    
23.              }
    
24.          }
    

26.          $request->header = array_change_key_case($header);
    
27.          $request->server = $_SERVER;
    
28.          $request->env    = $app->env;
    

30.          $inputData = $request->getInputData($request->input);
    

32.          $request->get     = $_GET;
    
33.          $request->post    = $_POST ?: $inputData;
    
34.          $request->put     = $inputData;
    
35.          $request->request = $_REQUEST;
    
36.          $request->cookie  = $_COOKIE;
    
37.          $request->file    = $_FILES ?? [];
    

39.          return $request;
    
40.  }
    

接管了这一系列信息之后,Request 类通过各种精细化接口对这些信息的局部获取全副进行输入,具体对外提供了那些接口,大家能够参考手册,这里对几个重要的接口进行剖析。

以后拜访域名或者 IP:host() 办法 



1.  // 获取拜访域名 vendortopthinkframeworksrcthinkRequest.php  1754 行
    
2.  public function host(bool $strict = false): string
    
3.  {4.          if ($this->host) {
    
5.              $host = $this->host;
    
6.          } else {
    

8.              // 通过 $_SERVER 中的 HTTP_X_FORWARDED_HOST 属性或者 HTTP_HOST 来获取
    
9.              $host = strval($this->server('HTTP_X_FORWARDED_HOST') ?: $this->server('HTTP_HOST'));
    
10.          }
    
11.          // 这里是判断要不要蕴含端口
    
12.          return true === $strict && strpos($host, ':') ? strstr($host, ':', true) : $host;
    
13.  }
    

以后残缺 URL:url() 办法



1.  // 获取以后残缺 URL 包含 QUERY_STRING 
    
2.  // vendortopthinkframeworksrcthinkRequest.php  460 行
    
3.  public function url(bool $complete = false): string
    
4.  {5.          if ($this->url) {
    
6.              $url = $this->url;
    
7.          } elseif ($this->server('HTTP_X_REWRITE_URL')) {8.              $url = $this->server('HTTP_X_REWRITE_URL');
    
9.          } elseif ($this->server('REQUEST_URI')) {10.              $url = $this->server('REQUEST_URI');
    
11.          } elseif ($this->server('ORIG_PATH_INFO')) {  // web 服务器重定向会呈现这个属性
    
12.              $url = $this->server('ORIG_PATH_INFO') . (!empty($this->server('QUERY_STRING')) ? '?' . $this->server('QUERY_STRING') : '');
    
13.          } elseif (isset($_SERVER['argv'][1])) {14.              $url = $_SERVER['argv'][1];
    
15.          } else {
    
16.              $url = '';
    
17.          }
    

19.          return $complete ? $this->domain() . $url : $url;
    
20.  }
    

2.2 输出变量:

输出变量是指能够变动的输出信息,那什么是能够变动的输出信息呢?比方,查问参数、上传文件、Post 申请体这些都是能够变动的,用户都是通过这些能够变动的货色去向服务器获取或者保存信息,比方通过查问参数的变动去获取不同的商品信息,通过提交图片信息保留本人的照片,这些对于框架而言都是输出变量,Request(申请) 类提供了丰盛的 API 帮咱们去获取这些信息,请看具体代码:

获取 $_POST 变量:post() 办法



1.  // 获取 POST 参数,传递属性名称就能够获取它对应的值
    
2.  // vendortopthinkframeworksrcthinkRequest.php  961 行
    
3.  public function post($name = '', $default = null, $filter ='')
    
4.  {5.          if (is_array($name)) {6.              return $this->only($name, $this->post, $filter);
    
7.          }
    

9.          return $this->input($this->post, $name, $default, $filter);
    
10.  }
    

13.  // 获取变量,并且反对过滤 不论是 get() post() 还是 param() 都依赖这个办法
    
14.  // vendortopthinkframeworksrcthinkRequest.php 1241 行
    
15.  public function input(array $data = [], $name = '', $default = null, $filter ='')
    
16.  {17.          if (false === $name) {
    
18.              // 获取原始数据
    
19.              return $data;
    
20.          }
    

22.          $name = (string) $name;
    
23.          if ('' != $name) {
    
24.              // 解析 name
    
25.              if (strpos($name, '/')) {26.                  [$name, $type] = explode('/', $name);
    
27.              }
    

29.              $data = $this->getData($data, $name);
    

31.              if (is_null($data)) {
    
32.                  return $default;
    
33.              }
    

35.              if (is_object($data)) {
    
36.                  return $data;
    
37.              }
    
38.          }
    

40.          $data = $this->filterData($data, $filter, $name, $default);
    

42.          if (isset($type) && $data !== $default) {
    
43.              // 强制类型转换
    
44.              $this->typeCast($data, $type);
    
45.          }
    

47.          return $data;
    
48.   }
    

获取 $_FILES 变量 (上传文件信息):file() 办法



1.  // 获取上传文件的信息,这里返回的是一个对象数组,每一个上传文件都会生成一个上传对象
    
2.  // vendortopthinkframeworksrcthinkRequest.php 1128 行
    
3.  public function file(string $name = '')
    
4.  {
    
5.          $files = $this->file; //
    
6.          if (!empty($files)) {8.              if (strpos($name, '.')) {9.                  [$name, $sub] = explode('.', $name);
    
10.              }
    

12.              // 解决上传文件
    
13.              $array = $this->dealUploadFile($files, $name);
    

15.              if ('' === $name) {
    
16.                  // 获取全副文件
    
17.                  return $array;
    
18.              } elseif (isset($sub) && isset($array[$name][$sub])) {19.                  return $array[$name][$sub];
    
20.              } elseif (isset($array[$name])) {21.                  return $array[$name];
    
22.              }
    
23.          }
    
24.  }
    

26.  // 上传文件对象创立 vendortopthinkframeworksrcthinkRequest.php 1175 行
    
27.  $item[] = new UploadedFile($temp['tmp_name'], $temp['name'], $temp['type'], $temp['error']);
    

29.  // 文件上传依赖 flysystem 组件这个咱们前面再介绍
    

2.3 申请类型:

申请类型是指 HTTP 申请类型,HTTP 总共五种申请类型,别离是:GET、POST、PUT、DELETE、HEAD。Request 类为咱们提供的接口分为两类,一类是获取以后申请类型,一类是判断是否是某种类型,接下为大家剖析两个代表性 API:

获取以后申请类型: method() 办法



1.  // 获取申请类型 申请类型的获取反对类型伪造,如果伪造申请类型优先获取伪造类型
    
2.  // vendortopthinkframeworksrcthinkRequest.php  717 行
    
3.  public function method(bool $origin = false): string
    
4.  {5.          if ($origin) {
    
6.              // 获取原始申请类型
    
7.              return $this->server('REQUEST_METHOD') ?: 'GET';
    
8.          } elseif (!$this->method) {9.              if (isset($this->post[$this->varMethod])) {10.                  $method = strtolower($this->post[$this->varMethod]);
    
11.                  if (in_array($method, ['get', 'post', 'put', 'patch', 'delete'])) {12.                      $this->method    = strtoupper($method);
    
13.                      $this->{$method} = $this->post;
    
14.                  } else {
    
15.                      $this->method = 'POST';
    
16.                  }
    
17.                  unset($this->post[$this->varMethod]);
    
18.              } elseif ($this->server('HTTP_X_HTTP_METHOD_OVERRIDE')) {19.                  $this->method = strtoupper($this->server('HTTP_X_HTTP_METHOD_OVERRIDE'));
    
20.              } else {21.                  $this->method = $this->server('REQUEST_METHOD') ?: 'GET';
    
22.              }
    
23.          }
    

25.          return $this->method;
    
26.  }
    

  判断是否 AJAX 申请: isAjax() 办法 



1.  // 判断是否为 ajax 申请 次要是通过 HTTP_X_REQUESTED_WITH 属性来判断,只有 ajax 申请才会有这个属性
    
2.  // ajax 申请同样反对类型伪造,并且无限获取伪造类型。3.  // vendortopthinkframeworksrcthinkRequest.php  1545 行
    
4.  public function isAjax(bool $ajax = false): bool
    
5.  {6.          $value  = $this->server('HTTP_X_REQUESTED_WITH');
    
7.          $result = $value && 'xmlhttprequest' == strtolower($value) ? true : false;
    

9.          if (true === $ajax) {
    
10.              return $result;
    
11.          }
    

13.          return $this->param($this->varAjax) ? true : $result;
    
14.  }
    

2.4 申请头信息:

申请头信息就是指 HTTP 申请报文头,以“属性名:属性值”的模式组织信息,服务端据此获取绝大部分客户端信息。Request 以两种形式获取此信息,第一种是从 $_SERVER 超全局数组中提取,第二种是 从 apache_request_headers 这个办法中获取 (前提是要采纳 Apache 作为 web 容器),看代码:



1.  // 在初始化办法中获取 vendortopthinkframeworksrcthinkRequest.php 307 行
    
2.  public static function __make(App $app)
    
3.  {4.          $request = new static();
    

6.          // 只有在 Apache 容器下才会有此办法
    
7.          if (function_exists('apache_request_headers') && $result = apache_request_headers()) {
    
8.              $header = $result;
    
9.          } else {
    
10.              // 没有的话从 $_SERVER 中提取
    
11.              $header = [];
    
12.              $server = $_SERVER;
    
13.              foreach ($server as $key => $val) {14.                  if (0 === strpos($key, 'HTTP_')) {15.                      $key          = str_replace('_', '-', strtolower(substr($key, 5)));
    
16.                      $header[$key] = $val;
    
17.                  }
    
18.              }
    
19.              if (isset($server['CONTENT_TYPE'])) {20.                  $header['content-type'] = $server['CONTENT_TYPE'];
    
21.              }
    
22.              if (isset($server['CONTENT_LENGTH'])) {23.                  $header['content-length'] = $server['CONTENT_LENGTH'];
    
24.              }
    
25.          }
    
26.  }
    

申请信息能够通过 header 办法获取:



1.  // 设置或者获取申请头 vendortopthinkframeworksrcthinkRequest.php 1221 行
    
2.  public function header(string $name = '', string $default = null)
    
3.  {4.          if ('' === $name) {
    
5.              return $this->header;
    
6.          }
    

8.          $name = str_replace('_', '-', strtolower($name));
    

10.          return $this->header[$name] ?? $default;
    
11.  }
    

2.5 申请缓存:

什么是申请缓存?就是将申请的内容缓存在客户端,下次申请的服务端,服务端只需响应状态码,无需响应内容,浏览器主动从缓存中读取,这样能大大晋升用户体验。这么好的性能是如何实现的呢?其实是通过全局中间件实现,那如何开启呢?看代码:



1.  <?php
    
2.  // 第一步:将全局缓存中间件的正文去掉
    
3.  // 全局中间件定义文件 appmiddleware.php
    
4.  return [
    
5.      // 全局申请缓存
    
6.       thinkmiddlewareCheckRequestCache::class,
    
7.      // 多语言加载
    
8.       thinkmiddlewareLoadLangPack::class,
    
9.      // Session 初始化
    
10.       thinkmiddlewareSessionInit::class
    
11.  ];
    

13.  // 第二步:将 路由配置中此项设置为 true
    
14.  // configroute.php  30 行
    

16.      // 是否开启申请缓存 true 主动缓存 反对设置申请缓存规定
    
17.      'request_cache_key'     => true,
    

  接下来揭晓实现原理,代码如下:

 1.      // 中间件入口函数 且只反对 get 申请
    
2.     public function handle($request, Closure $next, $cache = null)
    
3.     {4.          if ($request->isGet() && false !== $cache) {5.              $cache = $cache ?: $this->getRequestCache($request);
    

7.              if ($cache) {8.                  if (is_array($cache)) {9.                      [$key, $expire, $tag] = $cache;
    
10.                  } else {11.                      $key    = str_replace('|', '/', $request->url());
    
12.                      $expire = $cache;
    
13.                      $tag    = null;
    
14.                  }
    

16.                  if (strtotime($request->server('HTTP_IF_MODIFIED_SINCE', '')) + $expire > $request->server('REQUEST_TIME')) {
    
17.                      // 读取缓存
    
18.                      return Response::create()->code(304);
    
19.                  } elseif (($hit = $this->cache->get($key)) !== null) {20.                      [$content, $header, $when] = $hit;
    
21.                      if (null === $expire || $when + $expire > $request->server('REQUEST_TIME')) {22.                          return Response::create($content)->header($header);
    
23.                      }
    
24.                  }
    
25.              }
    
26.          }
    

28.          $response = $next($request);
    

30.          if (isset($key) && 200 == $response->getCode() && $response->isAllowCache()) {31.              $header                  = $response->getHeader();
    
32.              $header['Cache-Control'] = 'max-age=' . $expire . ',must-revalidate';
    
33.              $header['Last-Modified'] = gmdate('D, d M Y H:i:s') . 'GMT';
    
34.              $header['Expires']       = gmdate('D, d M Y H:i:s', time() + $expire) . 'GMT';
    

36.              $this->cache->tag($tag)->set($key, [$response->getContent(), $header, time()], $expire);
    
37.          }
    

39.          return $response;
    
40.      }

2.6 响应类型:

响应类型是服务端响应客户端内容的模式,框架实现七种响应类型别离是 File、Html、Json、Jsonp、Redirect、View、Xml。其实就是七个响应类,都继承 Response 类,并且重写了局部父类的办法,下图为七个类定义的地位:

      接下来别离介绍一下这个七个响应类所代表的含意:

      File : 通过批改响应头信息,实现文件下载。

     Html: 就是响应 html 页面,这是绝大数响应模式。

    Json: 响应 json 数据,用于 API 响应。

    Jsonp: 这是跨域申请响应。

    Redirect : 这是重定向。

    View: 这是响应视图实质还是 html 页面,这样做的益处就是无需调用视图外面 fetch 办法也能够渲染模板。

     Xml:这是响应 xml 数据。

2.7 响应输入:

响应输入其实在生命周期外面曾经介绍过了,这里再补充几个点。响应输入就是调用 Response 的 send 办法,在这个办法外面有一个重要的操作就是获取输入数据,看代码:



1.  public function send(): void
    
2.  {
    
3.          // 解决输入数据
    
4.          $data = $this->getContent();
    

6.          // 省略若干代码
    
7.  }
    

9.  public function getContent(): string
    
10.  {11.          if (null == $this->content) {12.              $content = $this->output($this->data);
    

14.              if (null !== $content && !is_string($content) && !is_numeric($content) && !is_callable([
    
15.                  $content,
    
16.                  '__toString',
    
17.              ])
    
18.              ) {19.                  throw new InvalidArgumentException(sprintf('variable type error:%s', gettype($content)));
    
20.              }
    

22.              $this->content = (string) $content;
    
23.          }
    

25.          return $this->content;
    
26.  }
    

output 办法,在除 Html 之外的六个类中都有被重写,这个办法决定输入内容的差别,这个给大家看一下 File 类的 output 办法。代码如下:



1.  // 此代码实现了一个文件下载性能 vendortopthinkframeworksrcthinkresponseFile.php
    
2.  protected function output($data)
    
3.  {4.          if (!$this->isContent && !is_file($data)) {5.              throw new Exception('file not exists:' . $data);
    
6.          }
    

8.          ob_end_clean();
    

10.          if (!empty($this->name)) {
    
11.              $name = $this->name;
    
12.          } else {13.              $name = !$this->isContent ? pathinfo($data, PATHINFO_BASENAME) : '';
    
14.          }
    

16.          if ($this->isContent) {
    
17.              $mimeType = $this->mimeType;
    
18.              $size     = strlen($data);
    
19.          } else {20.              $mimeType = $this->getMimeType($data);
    
21.              $size     = filesize($data);
    
22.          }
    

24.          $this->header['Pragma']                    = 'public';
    
25.          $this->header['Content-Type']              = $mimeType ?: 'application/octet-stream';
    
26.          $this->header['Cache-control']             = 'max-age=' . $this->expire;
    
27.          $this->header['Content-Disposition']       = ($this->force ? 'attachment;' : '') .'filename="'. $name .'"';
    
28.          $this->header['Content-Length']            = $size;
    
29.          $this->header['Content-Transfer-Encoding'] = 'binary';
    
30.          $this->header['Expires']                   = gmdate("D, d M Y H:i:s", time() + $this->expire) . 'GMT';
    

32.          $this->lastModified(gmdate('D, d M Y H:i:s', time()) . 'GMT');
    

34.          return $this->isContent ? $data : file_get_contents($data);
    
35.  }
    

2.8 响应参数:

所谓的响应参数就是指响应内容,状态码,响应头。尽管这些信息能够对立设置,同时 Response 也提供了独自的办法就设置这些内容,看代码演示:

响应内容



1.  // 输入数据设置
    
2.  public function data($data)
    
3.  {
    
4.          $this->data = $data;
    

6.          return $this;
    
7.  }
    

状态码



1.  // 设置 HTTP 状态码
    
2.  public function code(int $code)
    
3.  {
    
4.          $this->code = $code;
    

6.          return $this;
    
7.  }
    

响应头



1.  // 设置响应头
    
2.  public function header(array $header = [])
    
3.  {4.          $this->header = array_merge($this->header, $header);
    

6.          return $this;
    
7.  }
    

2.9 重定向:

重定向是属于响应类型的一种,这里就不多做介绍了,间接看框架的实现代码:



1.  // 实现就是通过 location 实现的 这里的 $data 为 url
    
2.  protected function output($data): string
    
3.  {4.          $this->header['Location'] = $data;
    

6.          return '';
    
7.  }
    

各位读者,对于申请与响应的介绍到这里就完结了,感激大家的浏览,如果对于文字教程意犹未尽,能够移步上行地址观看视频教程,并且能够自己一对一交换。

教程地址:https://edu.csdn.net/course/detail/28045

正文完
 0