从零实现一个 PHP 微框架 - 初始化请求
前言
更新一波文章。
这次的内容相对简单点,初始化请求的过程包括封装 $_GET
$_POST
等关联数组到 Request 对象中,用于后续流程的使用,以及从封装 Request 到路由之前的这段过程。
构造 Request
构造 Request 是通过 Application.dispatchToEmit
里的 $request = $this->make(Request::class)
初始化的,make
方法会通知容器初始化 Request
对象。
_12<?php_12protected function dispatchToEmit(): void_12{_12 // 获取请求_12 $request = $this->make(Request::class);_12_12 // 处理_12 $response = $this->make(RouteManager::class)->dispatch($request);_12_12 // 发送响应_12 $response->send();_12}
既然是通过容器来初始化的,那么就需要绑定该对象到容器,Request
对象是通过 RequestProvider
进行绑定的:
_19<?php_19_19namespace App\Providers;_19_19use App\Http\Request;_19_19class RequestProvider extends Provider_19{_19 public function register(): void_19 {_19 $this->app->singleton(_19 Request::class,_19 function () {_19 return Request::make();_19 },_19 'request'_19 );_19 }_19}
Request::make
从上面的代码可以看到初始化 Request
是通过 Request::make()
的静态工厂方法构造的:
_48<?php_48public static function make(_48 array $server = null,_48 array $query = null,_48 array $body = null,_48 array $cookies = null,_48 array $files = null_48): Request {_48 $files = Functions::convertFiles($files ?: $_FILES);_48 $server = $server ?: $_SERVER;_48 $uri =_48 isset($server['HTTPS']) && $server['HTTPS'] === 'on'_48 ? 'https://'_48 : 'http://';_48 if (isset($server['HTTP_HOST'])) {_48 $uri .= $server['HTTP_HOST'];_48 } else {_48 $uri .=_48 $server['SERVER_NAME'] ._48 (isset($server['SERVER_PORT']) &&_48 $server['SERVER_PORT'] !== '80' &&_48 $server['SERVER_PORT'] !== '443'_48 ? ':' . $server['SERVER_PORT']_48 : '');_48 }_48 $uri .= $server['REQUEST_URI'];_48 $protocol = '1.1';_48 if (isset($server['SERVER_PROTOCOL'])) {_48 preg_match(_48 '|^(HTTP/)?(?P<version>[1-9]\d*(?:\.\d)?)$|',_48 $server['SERVER_PROTOCOL'],_48 $matches_48 );_48 $protocol = $matches['version'];_48 }_48 return new static(_48 $server,_48 $files,_48 $uri,_48 $server['REQUEST_METHOD'],_48 'php://input',_48 Functions::parseHeaders($server),_48 $cookies ?: $_COOKIE,_48 $query ?: $_GET,_48 $body ?: $_POST,_48 $protocol_48 );_48}
首先是使用 Functions::convertFiles
方法将 $_FILES
关联数组转化到 UploadFile
数组,转化的步骤就不说明了,就是将数组的结构封装到对象(之所以要这么做是为了遵循 PSR 标准 )。
_34<?php_34public static function convertFiles(array $files): array_34{_34 $result = [];_34 foreach ($files as $key => $value) {_34 if ($value instanceof UploadedFileInterface) {_34 $result[$key] = $value;_34 continue;_34 }_34 if (_34 is_array($value) &&_34 isset($value['tmp_name']) &&_34 is_array($value['tmp_name'])_34 ) {_34 $result[$key] = self::resolveStructure($value);_34 continue;_34 }_34 if (is_array($value) && isset($value['tmp_name'])) {_34 $result[$key] = new UploadFile(_34 $value['tmp_name'],_34 $value['size'],_34 $value['error'],_34 $value['name'],_34 $value['type']_34 );_34 continue;_34 }_34 if (is_array($value)) {_34 $result[$key] = self::convertFiles($value);_34 continue;_34 }_34 }_34 return $result;_34}
然后是拼接 URL,由于 PHP 已经对 URL 进行切割,所以我们还需要拼接回去,以便后续的代码使用。以及 Protocol 的提取。
由于 PHP 将 Request header
存入了 $_SERVER
为了方便使用,我们需要把 $_SERVER
中带有 HTTP_
前缀的字段都提取出来 ,这些就是 Request header
,同时由于 header
是不区分大小写的,我们直接把 header
的名称转成小写即可。
_19<?php_19public static function parseHeaders(array $server): array_19{_19 $headers = [];_19 foreach ($server as $key => $value) {_19 if (!is_string($key)) {_19 continue;_19 }_19 if ($value === '') {_19 continue;_19 }_19 if (strpos($key, 'HTTP_') === 0) {_19 $name = str_replace('_', '-', strtolower(substr($key, 5)));_19 $headers[$name] = $value;_19 continue;_19 }_19 }_19 return $headers;_19}
new Request
有了上面的一些基础的信息,就可以正式的创建 Request
对象的:
_55<?php_55public function __construct(_55 array $server = [],_55 array $files = [],_55 $uri = '',_55 string $method = 'GET',_55 $body = 'php://input',_55 array $headers = [],_55 array $cookies = [],_55 array $query = [],_55 $parsed_body = null,_55 string $protocol = '1.1'_55) {_55 $this->validateFiles($files);_55 if ($body === 'php://input') {_55 $body = new Stream($body);_55 }_55 $this->setMethod($method);_55 if ($uri instanceof UriInterface) {_55 $this->uri = $uri;_55 } else {_55 $this->uri = new Uri($uri);_55 }_55 if ($body instanceof StreamInterface) {_55 $this->stream = $body;_55 } else {_55 $this->stream = new Stream($body, 'wb+');_55 }_55 $this->setHeaders($headers);_55 $this->server = $server;_55 $this->files = $files;_55 $this->cookies = $cookies;_55 $this->query = $query;_55 $this->protocol = $protocol;_55_55 $content_type = $this->header('Content-Type');_55 if (_55 $content_type !== null &&_55 stripos($content_type, 'application/json') !== false_55 ) {_55 $this->parsed_body = array_merge(_55 $parsed_body,_55 json_decode($this->stream->getContents(), true)_55 );_55 } else {_55 $this->parsed_body = $parsed_body;_55 }_55_55 if (!$this->hasHeader('Host') && $this->uri->getHost()) {_55 $host = $this->uri->getHost();_55 $host .= $this->uri->getPort() ? ':' . $this->uri->getPort() : '';_55 $this->headerAlias['host'] = 'Host';_55 $this->headers['Host'] = [$host];_55 }_55}
首先需要对 files
进行验证,判断 files
是否实现了 UploadedFileInterface
。
接着就需要对 Request body
进行封装了,Stream
是 StreamInterface
的实现类,提供了对 body
数据流的一些操作方法。
除了 file
和 body
,我们还需要把 url
封装成 Uri
对象,该对象实现了 UriInterface
,提供了对 url
的一些操作方法。
由于请求的方式可能是通过 JSON 的格式传输的,此时 $_POST
就无法获取到这些通过 JSON 传输的数据,所以,我们还需要解析 JSON。
在 PSR 标准中有说明,当请求没有 Host
头的时候,需要手动设置,保证 Request
对象中存在 Host
头。
结语
到这里初始化 Request 的部分就完成了。由于博主忙着重写 XK-Editor,所以更新 文章的速度可能会慢一点 2333。
从零实现一个 PHP 微框架 - 初始化请求
https://blog.ixk.me/post/implement-a-php-microframework-from-zero-6许可协议
发布于
2020-07-25
本文作者
Otstar Lin
转载或引用本文时请遵守许可协议,注明出处、不得用于商业用途!