从零实现一个 PHP 微框架 - 初始化请求
source link: https://blog.ixk.me/post/implement-a-php-microframework-from-zero-6
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
更新一波文章。
这次的内容相对简单点,初始化请求的过程包括封装 $_GET
$_POST
等关联数组到 Request 对象中,用于后续流程的使用,以及从封装 Request 到路由之前的这段过程。
构造 Request
构造 Request 是通过 Application.dispatchToEmit
里的 $request = $this->make(Request::class)
初始化的,make
方法会通知容器初始化 Request
对象。
1<?php 2protected function dispatchToEmit(): void 3{ 4 // 获取请求 5 $request = $this->make(Request::class); 6 7 // 处理 8 $response = $this->make(RouteManager::class)->dispatch($request); 9 10 // 发送响应 11 $response->send(); 12}
既然是通过容器来初始化的,那么就需要绑定该对象到容器,Request
对象是通过 RequestProvider
进行绑定的:
1<?php 2 3namespace App\Providers; 4 5use App\Http\Request; 6 7class RequestProvider extends Provider 8{ 9 public function register(): void 10 { 11 $this->app->singleton( 12 Request::class, 13 function () { 14 return Request::make(); 15 }, 16 'request' 17 ); 18 } 19}
Request::make
从上面的代码可以看到初始化 Request
是通过 Request::make()
的静态工厂方法构造的:
1<?php 2public static function make( 3 array $server = null, 4 array $query = null, 5 array $body = null, 6 array $cookies = null, 7 array $files = null 8): Request { 9 $files = Functions::convertFiles($files ?: $_FILES); 10 $server = $server ?: $_SERVER; 11 $uri = 12 isset($server['HTTPS']) && $server['HTTPS'] === 'on' 13 ? 'https://' 14 : 'http://'; 15 if (isset($server['HTTP_HOST'])) { 16 $uri .= $server['HTTP_HOST']; 17 } else { 18 $uri .= 19 $server['SERVER_NAME'] . 20 (isset($server['SERVER_PORT']) && 21 $server['SERVER_PORT'] !== '80' && 22 $server['SERVER_PORT'] !== '443' 23 ? ':' . $server['SERVER_PORT'] 24 : ''); 25 } 26 $uri .= $server['REQUEST_URI']; 27 $protocol = '1.1'; 28 if (isset($server['SERVER_PROTOCOL'])) { 29 preg_match( 30 '|^(HTTP/)?(?P<version>[1-9]\d*(?:\.\d)?)$|', 31 $server['SERVER_PROTOCOL'], 32 $matches 33 ); 34 $protocol = $matches['version']; 35 } 36 return new static( 37 $server, 38 $files, 39 $uri, 40 $server['REQUEST_METHOD'], 41 'php://input', 42 Functions::parseHeaders($server), 43 $cookies ?: $_COOKIE, 44 $query ?: $_GET, 45 $body ?: $_POST, 46 $protocol 47 ); 48}
首先是使用 Functions::convertFiles
方法将 $_FILES
关联数组转化到 UploadFile
数组,转化的步骤就不说明了,就是将数组的结构封装到对象(之所以要这么做是为了遵循 PSR 标准)。
1<?php 2public static function convertFiles(array $files): array 3{ 4 $result = []; 5 foreach ($files as $key => $value) { 6 if ($value instanceof UploadedFileInterface) { 7 $result[$key] = $value; 8 continue; 9 } 10 if ( 11 is_array($value) && 12 isset($value['tmp_name']) && 13 is_array($value['tmp_name']) 14 ) { 15 $result[$key] = self::resolveStructure($value); 16 continue; 17 } 18 if (is_array($value) && isset($value['tmp_name'])) { 19 $result[$key] = new UploadFile( 20 $value['tmp_name'], 21 $value['size'], 22 $value['error'], 23 $value['name'], 24 $value['type'] 25 ); 26 continue; 27 } 28 if (is_array($value)) { 29 $result[$key] = self::convertFiles($value); 30 continue; 31 } 32 } 33 return $result; 34}
然后是拼接 URL,由于 PHP 已经对 URL 进行切割,所以我们还需要拼接回去,以便后续的代码使用。以及 Protocol 的提取。
由于 PHP 将 Request header
存入了 $_SERVER
为了方便使用,我们需要把 $_SERVER
中带有 HTTP_
前缀的字段都提取出来,这些就是 Request header
,同时由于 header
是不区分大小写的,我们直接把 header
的名称转成小写即可。
1<?php 2public static function parseHeaders(array $server): array 3{ 4 $headers = []; 5 foreach ($server as $key => $value) { 6 if (!is_string($key)) { 7 continue; 8 } 9 if ($value === '') { 10 continue; 11 } 12 if (strpos($key, 'HTTP_') === 0) { 13 $name = str_replace('_', '-', strtolower(substr($key, 5))); 14 $headers[$name] = $value; 15 continue; 16 } 17 } 18 return $headers; 19}
new Request
有了上面的一些基础的信息,就可以正式的创建 Request
对象的:
1<?php 2public function __construct( 3 array $server = [], 4 array $files = [], 5 $uri = '', 6 string $method = 'GET', 7 $body = 'php://input', 8 array $headers = [], 9 array $cookies = [], 10 array $query = [], 11 $parsed_body = null, 12 string $protocol = '1.1' 13) { 14 $this->validateFiles($files); 15 if ($body === 'php://input') { 16 $body = new Stream($body); 17 } 18 $this->setMethod($method); 19 if ($uri instanceof UriInterface) { 20 $this->uri = $uri; 21 } else { 22 $this->uri = new Uri($uri); 23 } 24 if ($body instanceof StreamInterface) { 25 $this->stream = $body; 26 } else { 27 $this->stream = new Stream($body, 'wb+'); 28 } 29 $this->setHeaders($headers); 30 $this->server = $server; 31 $this->files = $files; 32 $this->cookies = $cookies; 33 $this->query = $query; 34 $this->protocol = $protocol; 35 36 $content_type = $this->header('Content-Type'); 37 if ( 38 $content_type !== null && 39 stripos($content_type, 'application/json') !== false 40 ) { 41 $this->parsed_body = array_merge( 42 $parsed_body, 43 json_decode($this->stream->getContents(), true) 44 ); 45 } else { 46 $this->parsed_body = $parsed_body; 47 } 48 49 if (!$this->hasHeader('Host') && $this->uri->getHost()) { 50 $host = $this->uri->getHost(); 51 $host .= $this->uri->getPort() ? ':' . $this->uri->getPort() : ''; 52 $this->headerAlias['host'] = 'Host'; 53 $this->headers['Host'] = [$host]; 54 } 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。
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK