1.5 服务端请求
RequestInterface提供HTTP请求消息的一般表示。但是,由于服务器端环境的性质,服务器端请求需要额外的处理。服务器端处理需要考虑通用网关接口(CGI),更具体地说,需要考虑PHP通过其服务器API(SAPI)对CGI的抽象和扩展。 PHP通过超级全局提供了关于输入编组的简化,例如:
- $ _COOKIE,反序列化并提供对HTTP cookie的简化访问。
- $ _GET,反序列化并提供对查询字符串参数的简化访问。
- $ _POST,反序列化并提供对通过HTTP POST提交的urlencoded参数的简化访问;通常,它可以被认为是解析消息体的结果。
- $ _FILES,提供有关文件上传的序列化元数据。
- $ _SERVER,提供对CGI / SAPI环境变量的访问,这些变量通常包括请求方法,请求方案,请求URI和标头。
ServerRequestInterface扩展了RequestInterface,以提供围绕这些各种超级全局的抽象。这种做法有助于减少消费者对超全球的耦合,并鼓励和提升测试请求消费者的能力。
服务器请求提供了一个附加属性“属性”,以允许消费者根据特定于应用程序的规则(例如路径匹配,方案匹配,主机匹配等)内省,分解和匹配请求。因此,服务器请求还可以在多个请求消费者之间提供消息传递。
1.6 上传文件
ServerRequestInterface指定用于在规范化结构中检索上载文件树的方法,每个叶子都是UploadedFileInterface的实例。
$ _FILES超全局在处理文件输入数组时有一些众所周知的问题。例如,如果您有一个提交文件数组的表单 - 例如输入名称“files”,提交文件[0]和文件[1] - PHP将表示为:
array(
'files' => array(
'name' => array(
0 => 'file0.txt',
1 => 'file1.html',
),
'type' => array(
0 => 'text/plain',
1 => 'text/html',
),
/* etc. */
),
)
而不是预期的:
array(
'files' => array(
0 => array(
'name' => 'file0.txt',
'type' => 'text/plain',
/* etc. */
),
1 => array(
'name' => 'file1.html',
'type' => 'text/html',
/* etc. */
),
),
)
结果是消费者需要知道这种语言实现细节,并编写用于收集给定上载数据的代码。
此外,存在以下情况:在发生文件上载时未填充$ _FILES:
- 当HTTP方法不是POST时。
- 单元测试时。
- 在非SAPI环境下运行时,例如ReactPHP。
在这种情况下,数据需要以不同方式播种。例如:
- 进程可能会解析邮件正文以发现文件上载。在这种情况下,实现可以选择不将文件上载写入文件系统,而是将它们包装在流中以减少内存,I / O和存储开销。
- 在单元测试场景中,开发人员需要能够存根和/或模拟文件上载元数据,以便验证和验证不同的场景。
引用的树结构应该模仿提交文件的命名结构。
在最简单的示例中,这可能是提交的单个命名表单元素:
<input type="file" name="avatar" />
在这种情况下,$ _FILES中的结构如下所示:
array(
'avatar' => array(
'tmp_name' => 'phpUxcOty',
'name' => 'my-avatar.png',
'size' => 90996,
'type' => 'image/png',
'error' => 0,
),
)
getUploadedFiles()返回的规范化表单将是:
array(
'avatar' => /* UploadedFileInterface instance */
)
对于使用数组表示法输入名称的输入:
<input type="file" name="my-form[details][avatar]" />
$ _FILES最终看起来像这样:
array(
'my-form' => array(
'details' => array(
'avatar' => array(
'tmp_name' => 'phpUxcOty',
'name' => 'my-avatar.png',
'size' => 90996,
'type' => 'image/png',
'error' => 0,
),
),
),
)
getUploadedFiles()返回的相应树应该是:
array(
'my-form' => array(
'details' => array(
'avatar' => /* UploadedFileInterface instance */
),
),
)
在某些情况下,您可以指定一个文件数组:
Upload an avatar: <input type="file" name="my-form[details][avatars][]" />
Upload an avatar: <input type="file" name="my-form[details][avatars][]" />
(例如,JavaScript控件可能会产生额外的文件上传输入,以允许一次上传多个文件。)
在这种情况下,规范实现必须聚合与给定索引处的文件相关的所有信息。原因是因为$ _FILES在这种情况下偏离其正常结构:
array(
'my-form' => array(
'details' => array(
'avatars' => array(
'tmp_name' => array(
0 => '...',
1 => '...',
2 => '...',
),
'name' => array(
0 => '...',
1 => '...',
2 => '...',
),
'size' => array(
0 => '...',
1 => '...',
2 => '...',
),
'type' => array(
0 => '...',
1 => '...',
2 => '...',
),
'error' => array(
0 => '...',
1 => '...',
2 => '...',
),
),
),
),
)
上面的$ _FILES数组将对应于getUploadedFiles()返回的以下结构
array(
'my-form' => array(
'details' => array(
'avatars' => array(
0 => /* UploadedFileInterface instance */,
1 => /* UploadedFileInterface instance */,
2 => /* UploadedFileInterface instance */,
),
),
),
)
消费者将使用以下方法访问嵌套数组的索引1:
$request->getUploadedFiles()['my-form']['details']['avatars'][1];
由于上传的文件数据是派生的(派生自$ _FILES或请求体),因此接口中也存在mutator方法withUploadedFiles(),允许将规范化委托给另一个进程。
在原始示例的情况下,消费类似于以下内容:
$file0 = $request->getUploadedFiles()['files'][0];
$file1 = $request->getUploadedFiles()['files'][1];
printf(
"Received the files %s and %s",
$file0->getClientFilename(),
$file1->getClientFilename()
);
// "Received the files file0.txt and file1.html"
该提案还认识到实现可以在非SAPI环境中运行。因此,UploadedFileInterface提供了确保操作无论环境如何都可以工作的方法。特别是:
- moveTo($ targetPath)作为直接在临时上载文件上调用move_uploaded_file()的安全和推荐的替代方法提供。实现将根据环境检测正确的操作。
- getStream()将返回一个StreamInterface实例。在非SAPI环境中,一种建议的可能性是将单个上载文件解析为php:// temp streams而不是直接解析到文件;在这种情况下,不存在上传文件。因此,无论环境如何,getStream()都可以保证工作。
例如:
// Move a file to an upload directory
$filename = sprintf(
'%s.%s',
create_uuid(),
pathinfo($file0->getClientFilename(), PATHINFO_EXTENSION)
);
$file0->moveTo(DATA_DIR . '/' . $filename);
// Stream a file to Amazon S3.
// Assume $s3wrapper is a PHP stream that will write to S3, and that
// Psr7StreamWrapper is a class that will decorate a StreamInterface as a PHP
// StreamWrapper.
$stream = new Psr7StreamWrapper($file1->getStream());
stream_copy_to_stream($stream, $s3wrapper);
2 包装
所描述的接口和类作为psr / http-message包的一部分提供。