$_FILES 键用于构建 PSR-7 上传文件列表
$_FILES key used for building a PSR-7 uploaded files list
短版:
当用户使用表单上传文件时,全局变量$_FILES
中保存了一个数组。例如,当使用:
<input type="file" name="myfiles0" />
全局变量如下所示:
$_FILES = [
'myfiles0' => [
'name' => 'image-1.jpg',
'type' => 'image/jpeg',
'tmp_name' => '[path-to]/tmp/php/phptiV897',
'error' => 0,
'size' => 92738,
],
]
原则上,我需要知道数组 $_FILES['myfiles0']
中的哪些键始终存在并且(可能)始终设置,无论其他键看起来如何,或使用哪种浏览器。你能告诉我吗?
请注意,$_FILES
变量还可以包含使用数组表示法上传的文件的多维数组,如下所示:
<input type="file" name="myfiles1[demo][images][]" multiple />
长版:
为了实现 PSR-7 Uploaded files,我需要对上传的文件列表进行 规范化 。初始列表可以由用户提供,也可以是使用表单上传标准文件的结果,例如$_FILES
全局变量。对于规范化过程,我需要检查以下标准文件上传密钥之一的存在和 "correctness"(可能这个词的选择不当):
name
type
tmp_name
error
size
原则上,如果在提供的上传文件列表(也可以是多维数组)中找到选择的key(我暂时选择tmp_name
),则为假设key所属的数组项是一个标准的文件上传数组项,包含上面的key列表。否则,例如如果未找到所选键,则假定相应的数组项是 UploadedFileInterface.
的实例
不幸的是,在标准文件上传的情况下,我无法在任何地方找到关于哪个键(来自上面的列表)总是存在并且(可能)总是在 $_FILES
变量中设置的信息,无论其他列表键看起来如何,或者使用哪个浏览器。
如果你能在这件事上帮助我,我将不胜感激。
谢谢。
我决定使用 tmp_name
密钥进行文件上传验证。
很遗憾,我很久以前就做出了这个决定。所以我不记得所有支持它的论点了,这是我阅读的文档和我执行的测试的结果。虽然,其中一个论点是,与其他键相比,tmp_name
键的值在客户端不能是 set/changed。应用程序所在的环境 运行 决定应为其设置哪个值。
我将在此处 post 我当时写的 PSR-7 & PSR-17 实现的最终版本(关于 上传的文件 )。也许对某人有帮助。
执行ServerRequestFactoryInterface
:
它读取上传文件列表(在 $_FILES
中找到,或作为参数手动传递),如果尚未完成,则将其转换为 "a normalized tree of upload metadata, with each leaf an instance of Psr\Http\Message\UploadedFileInterface"(参见 PSR-7 中的 “1.6 上传文件”)。
然后它创建一个 ServerRequestInterface
实例,将规范化的上传文件列表传递给它。
<?php
namespace MyLib\Http\Message\Factory\SapiServerRequestFactory;
use MyLib\Http\Message\Uri;
use MyLib\Http\Message\Stream;
use MyLib\Http\Message\UploadedFile;
use MyLib\Http\Message\ServerRequest;
use Psr\Http\Message\UploadedFileInterface;
use Psr\Http\Message\ServerRequestInterface;
use MyLib\Http\Message\Factory\ServerRequestFactory;
use Fig\Http\Message\RequestMethodInterface as RequestMethod;
/**
* Server request factory for the "apache2handler" SAPI.
*/
class Apache2HandlerFactory extends ServerRequestFactory {
/**
* Create a new server request by seeding the generated request
* instance with the elements of the given array of SAPI parameters.
*
* @param array $serverParams (optional) Array of SAPI parameters with which to seed
* the generated request instance.
* @return ServerRequestInterface The new server request.
*/
public function createServerRequestFromArray(array $serverParams = []): ServerRequestInterface {
if (!$serverParams) {
$serverParams = $_SERVER;
}
$this->headers = $this->buildHeaders($serverParams);
$method = $this->buildMethod($serverParams);
$uri = $this->buildUri($serverParams, $this->headers);
$this->parsedBody = $this->buildParsedBody($this->parsedBody, $method, $this->headers);
$this->queryParams = $this->queryParams ?: $_GET;
$this->uploadedFiles = $this->buildUploadedFiles($this->uploadedFiles ?: $_FILES);
$this->cookieParams = $this->buildCookieParams($this->headers, $this->cookieParams);
$this->protocolVersion = $this->buildProtocolVersion($serverParams, $this->protocolVersion);
return parent::createServerRequest($method, $uri, $serverParams);
}
/*
* Custom methods.
*/
// [... All other methods ...]
/**
* Build the list of uploaded files as a normalized tree of upload metadata,
* with each leaf an instance of Psr\Http\Message\UploadedFileInterface.
*
* Not part of PSR-17.
*
* @param array $uploadedFiles The list of uploaded files (normalized or not).
* Data MAY come from $_FILES or the message body.
* @return array A tree of upload files in a normalized structure, with each leaf
* an instance of UploadedFileInterface.
*/
private function buildUploadedFiles(array $uploadedFiles) {
return $this->normalizeUploadedFiles($uploadedFiles);
}
/**
* Normalize - if not already - the list of uploaded files as a tree of upload
* metadata, with each leaf an instance of Psr\Http\Message\UploadedFileInterface.
*
* Not part of PSR-17.
*
* IMPORTANT: For a correct normalization of the uploaded files list, the FIRST OCCURRENCE
* of the key "tmp_name" is checked against. See "POST method uploads" link.
* As soon as the key will be found in an item of the uploaded files list, it
* will be supposed that the array item to which it belongs is an array with
* a structure similar to the one saved in the global variable $_FILES when a
* standard file upload is executed.
*
* @link https://secure.php.net/manual/en/features.file-upload.post-method.php POST method uploads.
* @link https://secure.php.net/manual/en/reserved.variables.files.php $_FILES.
* @link https://tools.ietf.org/html/rfc1867 Form-based File Upload in HTML.
* @link https://tools.ietf.org/html/rfc2854 The 'text/html' Media Type.
*
* @param array $uploadedFiles The list of uploaded files (normalized or not). Data MAY come
* from $_FILES or the message body.
* @return array A tree of upload files in a normalized structure, with each leaf
* an instance of UploadedFileInterface.
* @throws \InvalidArgumentException An invalid structure of uploaded files list is provided.
*/
private function normalizeUploadedFiles(array $uploadedFiles) {
$normalizedUploadedFiles = [];
foreach ($uploadedFiles as $key => $item) {
if (is_array($item)) {
$normalizedUploadedFiles[$key] = array_key_exists('tmp_name', $item) ?
$this->normalizeFileUploadItem($item) :
$this->normalizeUploadedFiles($item);
} elseif ($item instanceof UploadedFileInterface) {
$normalizedUploadedFiles[$key] = $item;
} else {
throw new \InvalidArgumentException(
'The structure of the uploaded files list is not valid.'
);
}
}
return $normalizedUploadedFiles;
}
/**
* Normalize the file upload item which contains the FIRST OCCURRENCE of the key "tmp_name".
*
* This method returns a tree structure, with each leaf
* an instance of Psr\Http\Message\UploadedFileInterface.
*
* Not part of PSR-17.
*
* @param array $item The file upload item.
* @return array The file upload item as a tree structure, with each leaf
* an instance of UploadedFileInterface.
* @throws \InvalidArgumentException The value at the key "tmp_name" is empty.
*/
private function normalizeFileUploadItem(array $item) {
// Validate the value at the key "tmp_name".
if (empty($item['tmp_name'])) {
throw new \InvalidArgumentException(
'The value of the key "tmp_name" in the uploaded files list '
. 'must be a non-empty value or a non-empty array.'
);
}
// Get the value at the key "tmp_name".
$filename = $item['tmp_name'];
// Return the normalized value at the key "tmp_name".
if (is_array($filename)) {
return $this->normalizeFileUploadTmpNameItem($filename, $item);
}
// Get the leaf values.
$size = $item['size'] ?? null;
$error = $item['error'] ?? \UPLOAD_ERR_OK;
$clientFilename = $item['name'] ?? null;
$clientMediaType = $item['type'] ?? null;
// Return an instance of UploadedFileInterface.
return $this->createUploadedFile(
$filename
, $size
, $error
, $clientFilename
, $clientMediaType
);
}
/**
* Normalize the array assigned as value to the FIRST OCCURRENCE of the key "tmp_name" in a
* file upload item of the uploaded files list. It is recursively iterated, in order to build
* a tree structure, with each leaf an instance of Psr\Http\Message\UploadedFileInterface.
*
* Not part of PSR-17.
*
* @param array $item The array assigned as value to the FIRST OCCURRENCE of the key "tmp_name".
* @param array $currentElements An array holding the file upload key/value pairs
* of the current item.
* @return array A tree structure, with each leaf an instance of UploadedFileInterface.
* @throws \InvalidArgumentException
*/
private function normalizeFileUploadTmpNameItem(array $item, array $currentElements) {
$normalizedItem = [];
foreach ($item as $key => $value) {
if (is_array($value)) {
// Validate the values at the keys "size" and "error".
if (
!isset($currentElements['size'][$key]) ||
!is_array($currentElements['size'][$key]) ||
!isset($currentElements['error'][$key]) ||
!is_array($currentElements['error'][$key])
) {
throw new \InvalidArgumentException(
'The structure of the items assigned to the keys "size" and "error" '
. 'in the uploaded files list must be identical with the one of the '
. 'item assigned to the key "tmp_name". This restriction does not '
. 'apply to the leaf elements.'
);
}
// Get the array values.
$filename = $currentElements['tmp_name'][$key];
$size = $currentElements['size'][$key];
$error = $currentElements['error'][$key];
$clientFilename = isset($currentElements['name'][$key]) &&
is_array($currentElements['name'][$key]) ?
$currentElements['name'][$key] :
null;
$clientMediaType = isset($currentElements['type'][$key]) &&
is_array($currentElements['type'][$key]) ?
$currentElements['type'][$key] :
null;
// Normalize recursively.
$normalizedItem[$key] = $this->normalizeFileUploadTmpNameItem($value, [
'tmp_name' => $filename,
'size' => $size,
'error' => $error,
'name' => $clientFilename,
'type' => $clientMediaType,
]);
} else {
// Get the leaf values.
$filename = $currentElements['tmp_name'][$key];
$size = $currentElements['size'][$key] ?? null;
$error = $currentElements['error'][$key] ?? \UPLOAD_ERR_OK;
$clientFilename = $currentElements['name'][$key] ?? null;
$clientMediaType = $currentElements['type'][$key] ?? null;
// Create an instance of UploadedFileInterface.
$normalizedItem[$key] = $this->createUploadedFile(
$filename
, $size
, $error
, $clientFilename
, $clientMediaType
);
}
}
return $normalizedItem;
}
/**
* Create an instance of UploadedFileInterface.
*
* Not part of PSR-17.
*
* @param string $filename The filename of the uploaded file.
* @param int|null $size (optional) The file size in bytes or null if unknown.
* @param int $error (optional) The error associated with the uploaded file. The value MUST be
* one of PHP's UPLOAD_ERR_XXX constants.
* @param string|null $clientFilename (optional) The filename sent by the client, if any.
* @param string|null $clientMediaType (optional) The media type sent by the client, if any.
* @return UploadedFileInterface
*/
private function createUploadedFile(
string $filename
, int $size = null
, int $error = \UPLOAD_ERR_OK
, string $clientFilename = null
, string $clientMediaType = null
): UploadedFileInterface {
// Create a stream with read-only access.
$stream = new Stream($filename, 'rb');
return new UploadedFile($stream, $size, $error, $clientFilename, $clientMediaType);
}
}
基础classServerRequestFactory
:
<?php
namespace MyLib\Http\Message\Factory;
use MyLib\Http\Message\Uri;
use Psr\Http\Message\UriInterface;
use Psr\Http\Message\StreamInterface;
use MyLib\Http\Message\ServerRequest;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ServerRequestFactoryInterface;
/**
* Server request factory.
*/
class ServerRequestFactory implements ServerRequestFactoryInterface {
/**
* Message body.
*
* @var StreamInterface
*/
protected $body;
/**
* Attributes list.
*
* @var array
*/
protected $attributes = [];
/**
* Headers list with case-insensitive header names.
* A header value can be a string, or an array of strings.
*
* [
* 'header-name 1' => 'header-value',
* 'header-name 2' => [
* 'header-value 1',
* 'header-value 2',
* ],
* ]
*
* @link https://tools.ietf.org/html/rfc7230#section-3.2 Header Fields.
* @link https://tools.ietf.org/html/rfc7231#section-5 Request Header Fields.
*
* @var array
*/
protected $headers = [];
/**
* Parsed body, e.g. the deserialized body parameters, if any.
*
* @var null|array|object
*/
protected $parsedBody;
/**
* Query string arguments.
*
* @var array
*/
protected $queryParams = [];
/**
* Uploaded files.
*
* @var array
*/
protected $uploadedFiles = [];
/**
* Cookies.
*
* @var array
*/
protected $cookieParams = [];
/**
* HTTP protocol version.
*
* @var string
*/
protected $protocolVersion;
/**
*
* @param StreamInterface $body Message body.
* @param array $attributes (optional) Attributes list.
* @param array $headers (optional) Headers list with case-insensitive header names.
* A header value can be a string, or an array of strings.
* @param null|array|object $parsedBody (optional) Parsed body, e.g. the deserialized body
* parameters, if any. The data IS NOT REQUIRED to come from $_POST, but MUST be the
* results of deserializing the request body content.
* @param array $queryParams (optional) Query string arguments. They MAY be injected from
* PHP's $_GET superglobal, or MAY be derived from some other value such as the URI.
* @param array $uploadedFiles (optional) Uploaded files list as a normalized tree of upload
* metadata, with each leaf an instance of Psr\Http\Message\UploadedFileInterface.
* @param array $cookieParams (optional) Cookies. The data IS NOT REQUIRED to come from
* the $_COOKIE superglobal, but MUST be compatible with the structure of $_COOKIE.
* @param string $protocolVersion (optional) HTTP protocol version.
*/
public function __construct(
StreamInterface $body
, array $attributes = []
, array $headers = []
, $parsedBody = null
, array $queryParams = []
, array $uploadedFiles = []
, array $cookieParams = []
, string $protocolVersion = '1.1'
) {
$this->body = $body;
$this->attributes = $attributes;
$this->headers = $headers;
$this->parsedBody = $parsedBody;
$this->queryParams = $queryParams;
$this->uploadedFiles = $uploadedFiles;
$this->cookieParams = $cookieParams;
$this->protocolVersion = $protocolVersion;
}
/**
* Create a new server request.
*
* Note that server-params are taken precisely as given - no parsing/processing
* of the given values is performed, and, in particular, no attempt is made to
* determine the HTTP method or URI, which must be provided explicitly.
*
* @param string $method The HTTP method associated with the request.
* @param UriInterface|string $uri The URI associated with the request. If
* the value is a string, the factory MUST create a UriInterface
* instance based on it.
* @param array $serverParams Array of SAPI parameters with which to seed
* the generated request instance.
*
* @return ServerRequestInterface
*/
public function createServerRequest(
string $method
, $uri
, array $serverParams = []
): ServerRequestInterface {
// Validate method and URI.
$this
->validateMethod($method)
->validateUri($uri)
;
// Create an instance of UriInterface.
if (is_string($uri)) {
$uri = new Uri($uri);
}
// Create the server request.
return new ServerRequest(
$method
, $uri
, $this->body
, $this->attributes
, $this->headers
, $serverParams
, $this->parsedBody
, $this->queryParams
, $this->uploadedFiles
, $this->cookieParams
, $this->protocolVersion
);
}
// [... Other methods ...]
}
通过 ServerRequestFactoryInterface
实现创建 ServerRequestInterface
实例:
<?php
use MyLib\Http\Message\Factory\SapiServerRequestFactory\Apache2HandlerFactory;
// [...]
// Create stream with read-only access.
$body = $streamFactory->createStreamFromFile('php://temp', 'rb');
$serverRequestFactory = new Apache2HandlerFactory(
$body
, [] /* attributes */
, [] /* headers */
, $_POST /* parsed body */
, $_GET /* query params */
, $_FILES /* uploaded files */
, $_COOKIE /* cookie params */
, '1.1' /* http protocol version */
);
$serverRequest = $serverRequestFactory->createServerRequestFromArray($_SERVER);
// [...]
短版:
当用户使用表单上传文件时,全局变量$_FILES
中保存了一个数组。例如,当使用:
<input type="file" name="myfiles0" />
全局变量如下所示:
$_FILES = [
'myfiles0' => [
'name' => 'image-1.jpg',
'type' => 'image/jpeg',
'tmp_name' => '[path-to]/tmp/php/phptiV897',
'error' => 0,
'size' => 92738,
],
]
原则上,我需要知道数组 $_FILES['myfiles0']
中的哪些键始终存在并且(可能)始终设置,无论其他键看起来如何,或使用哪种浏览器。你能告诉我吗?
请注意,$_FILES
变量还可以包含使用数组表示法上传的文件的多维数组,如下所示:
<input type="file" name="myfiles1[demo][images][]" multiple />
长版:
为了实现 PSR-7 Uploaded files,我需要对上传的文件列表进行 规范化 。初始列表可以由用户提供,也可以是使用表单上传标准文件的结果,例如$_FILES
全局变量。对于规范化过程,我需要检查以下标准文件上传密钥之一的存在和 "correctness"(可能这个词的选择不当):
name
type
tmp_name
error
size
原则上,如果在提供的上传文件列表(也可以是多维数组)中找到选择的key(我暂时选择tmp_name
),则为假设key所属的数组项是一个标准的文件上传数组项,包含上面的key列表。否则,例如如果未找到所选键,则假定相应的数组项是 UploadedFileInterface.
不幸的是,在标准文件上传的情况下,我无法在任何地方找到关于哪个键(来自上面的列表)总是存在并且(可能)总是在 $_FILES
变量中设置的信息,无论其他列表键看起来如何,或者使用哪个浏览器。
如果你能在这件事上帮助我,我将不胜感激。
谢谢。
我决定使用 tmp_name
密钥进行文件上传验证。
很遗憾,我很久以前就做出了这个决定。所以我不记得所有支持它的论点了,这是我阅读的文档和我执行的测试的结果。虽然,其中一个论点是,与其他键相比,tmp_name
键的值在客户端不能是 set/changed。应用程序所在的环境 运行 决定应为其设置哪个值。
我将在此处 post 我当时写的 PSR-7 & PSR-17 实现的最终版本(关于 上传的文件 )。也许对某人有帮助。
执行ServerRequestFactoryInterface
:
它读取上传文件列表(在 $_FILES
中找到,或作为参数手动传递),如果尚未完成,则将其转换为 "a normalized tree of upload metadata, with each leaf an instance of Psr\Http\Message\UploadedFileInterface"(参见 PSR-7 中的 “1.6 上传文件”)。
然后它创建一个 ServerRequestInterface
实例,将规范化的上传文件列表传递给它。
<?php
namespace MyLib\Http\Message\Factory\SapiServerRequestFactory;
use MyLib\Http\Message\Uri;
use MyLib\Http\Message\Stream;
use MyLib\Http\Message\UploadedFile;
use MyLib\Http\Message\ServerRequest;
use Psr\Http\Message\UploadedFileInterface;
use Psr\Http\Message\ServerRequestInterface;
use MyLib\Http\Message\Factory\ServerRequestFactory;
use Fig\Http\Message\RequestMethodInterface as RequestMethod;
/**
* Server request factory for the "apache2handler" SAPI.
*/
class Apache2HandlerFactory extends ServerRequestFactory {
/**
* Create a new server request by seeding the generated request
* instance with the elements of the given array of SAPI parameters.
*
* @param array $serverParams (optional) Array of SAPI parameters with which to seed
* the generated request instance.
* @return ServerRequestInterface The new server request.
*/
public function createServerRequestFromArray(array $serverParams = []): ServerRequestInterface {
if (!$serverParams) {
$serverParams = $_SERVER;
}
$this->headers = $this->buildHeaders($serverParams);
$method = $this->buildMethod($serverParams);
$uri = $this->buildUri($serverParams, $this->headers);
$this->parsedBody = $this->buildParsedBody($this->parsedBody, $method, $this->headers);
$this->queryParams = $this->queryParams ?: $_GET;
$this->uploadedFiles = $this->buildUploadedFiles($this->uploadedFiles ?: $_FILES);
$this->cookieParams = $this->buildCookieParams($this->headers, $this->cookieParams);
$this->protocolVersion = $this->buildProtocolVersion($serverParams, $this->protocolVersion);
return parent::createServerRequest($method, $uri, $serverParams);
}
/*
* Custom methods.
*/
// [... All other methods ...]
/**
* Build the list of uploaded files as a normalized tree of upload metadata,
* with each leaf an instance of Psr\Http\Message\UploadedFileInterface.
*
* Not part of PSR-17.
*
* @param array $uploadedFiles The list of uploaded files (normalized or not).
* Data MAY come from $_FILES or the message body.
* @return array A tree of upload files in a normalized structure, with each leaf
* an instance of UploadedFileInterface.
*/
private function buildUploadedFiles(array $uploadedFiles) {
return $this->normalizeUploadedFiles($uploadedFiles);
}
/**
* Normalize - if not already - the list of uploaded files as a tree of upload
* metadata, with each leaf an instance of Psr\Http\Message\UploadedFileInterface.
*
* Not part of PSR-17.
*
* IMPORTANT: For a correct normalization of the uploaded files list, the FIRST OCCURRENCE
* of the key "tmp_name" is checked against. See "POST method uploads" link.
* As soon as the key will be found in an item of the uploaded files list, it
* will be supposed that the array item to which it belongs is an array with
* a structure similar to the one saved in the global variable $_FILES when a
* standard file upload is executed.
*
* @link https://secure.php.net/manual/en/features.file-upload.post-method.php POST method uploads.
* @link https://secure.php.net/manual/en/reserved.variables.files.php $_FILES.
* @link https://tools.ietf.org/html/rfc1867 Form-based File Upload in HTML.
* @link https://tools.ietf.org/html/rfc2854 The 'text/html' Media Type.
*
* @param array $uploadedFiles The list of uploaded files (normalized or not). Data MAY come
* from $_FILES or the message body.
* @return array A tree of upload files in a normalized structure, with each leaf
* an instance of UploadedFileInterface.
* @throws \InvalidArgumentException An invalid structure of uploaded files list is provided.
*/
private function normalizeUploadedFiles(array $uploadedFiles) {
$normalizedUploadedFiles = [];
foreach ($uploadedFiles as $key => $item) {
if (is_array($item)) {
$normalizedUploadedFiles[$key] = array_key_exists('tmp_name', $item) ?
$this->normalizeFileUploadItem($item) :
$this->normalizeUploadedFiles($item);
} elseif ($item instanceof UploadedFileInterface) {
$normalizedUploadedFiles[$key] = $item;
} else {
throw new \InvalidArgumentException(
'The structure of the uploaded files list is not valid.'
);
}
}
return $normalizedUploadedFiles;
}
/**
* Normalize the file upload item which contains the FIRST OCCURRENCE of the key "tmp_name".
*
* This method returns a tree structure, with each leaf
* an instance of Psr\Http\Message\UploadedFileInterface.
*
* Not part of PSR-17.
*
* @param array $item The file upload item.
* @return array The file upload item as a tree structure, with each leaf
* an instance of UploadedFileInterface.
* @throws \InvalidArgumentException The value at the key "tmp_name" is empty.
*/
private function normalizeFileUploadItem(array $item) {
// Validate the value at the key "tmp_name".
if (empty($item['tmp_name'])) {
throw new \InvalidArgumentException(
'The value of the key "tmp_name" in the uploaded files list '
. 'must be a non-empty value or a non-empty array.'
);
}
// Get the value at the key "tmp_name".
$filename = $item['tmp_name'];
// Return the normalized value at the key "tmp_name".
if (is_array($filename)) {
return $this->normalizeFileUploadTmpNameItem($filename, $item);
}
// Get the leaf values.
$size = $item['size'] ?? null;
$error = $item['error'] ?? \UPLOAD_ERR_OK;
$clientFilename = $item['name'] ?? null;
$clientMediaType = $item['type'] ?? null;
// Return an instance of UploadedFileInterface.
return $this->createUploadedFile(
$filename
, $size
, $error
, $clientFilename
, $clientMediaType
);
}
/**
* Normalize the array assigned as value to the FIRST OCCURRENCE of the key "tmp_name" in a
* file upload item of the uploaded files list. It is recursively iterated, in order to build
* a tree structure, with each leaf an instance of Psr\Http\Message\UploadedFileInterface.
*
* Not part of PSR-17.
*
* @param array $item The array assigned as value to the FIRST OCCURRENCE of the key "tmp_name".
* @param array $currentElements An array holding the file upload key/value pairs
* of the current item.
* @return array A tree structure, with each leaf an instance of UploadedFileInterface.
* @throws \InvalidArgumentException
*/
private function normalizeFileUploadTmpNameItem(array $item, array $currentElements) {
$normalizedItem = [];
foreach ($item as $key => $value) {
if (is_array($value)) {
// Validate the values at the keys "size" and "error".
if (
!isset($currentElements['size'][$key]) ||
!is_array($currentElements['size'][$key]) ||
!isset($currentElements['error'][$key]) ||
!is_array($currentElements['error'][$key])
) {
throw new \InvalidArgumentException(
'The structure of the items assigned to the keys "size" and "error" '
. 'in the uploaded files list must be identical with the one of the '
. 'item assigned to the key "tmp_name". This restriction does not '
. 'apply to the leaf elements.'
);
}
// Get the array values.
$filename = $currentElements['tmp_name'][$key];
$size = $currentElements['size'][$key];
$error = $currentElements['error'][$key];
$clientFilename = isset($currentElements['name'][$key]) &&
is_array($currentElements['name'][$key]) ?
$currentElements['name'][$key] :
null;
$clientMediaType = isset($currentElements['type'][$key]) &&
is_array($currentElements['type'][$key]) ?
$currentElements['type'][$key] :
null;
// Normalize recursively.
$normalizedItem[$key] = $this->normalizeFileUploadTmpNameItem($value, [
'tmp_name' => $filename,
'size' => $size,
'error' => $error,
'name' => $clientFilename,
'type' => $clientMediaType,
]);
} else {
// Get the leaf values.
$filename = $currentElements['tmp_name'][$key];
$size = $currentElements['size'][$key] ?? null;
$error = $currentElements['error'][$key] ?? \UPLOAD_ERR_OK;
$clientFilename = $currentElements['name'][$key] ?? null;
$clientMediaType = $currentElements['type'][$key] ?? null;
// Create an instance of UploadedFileInterface.
$normalizedItem[$key] = $this->createUploadedFile(
$filename
, $size
, $error
, $clientFilename
, $clientMediaType
);
}
}
return $normalizedItem;
}
/**
* Create an instance of UploadedFileInterface.
*
* Not part of PSR-17.
*
* @param string $filename The filename of the uploaded file.
* @param int|null $size (optional) The file size in bytes or null if unknown.
* @param int $error (optional) The error associated with the uploaded file. The value MUST be
* one of PHP's UPLOAD_ERR_XXX constants.
* @param string|null $clientFilename (optional) The filename sent by the client, if any.
* @param string|null $clientMediaType (optional) The media type sent by the client, if any.
* @return UploadedFileInterface
*/
private function createUploadedFile(
string $filename
, int $size = null
, int $error = \UPLOAD_ERR_OK
, string $clientFilename = null
, string $clientMediaType = null
): UploadedFileInterface {
// Create a stream with read-only access.
$stream = new Stream($filename, 'rb');
return new UploadedFile($stream, $size, $error, $clientFilename, $clientMediaType);
}
}
基础classServerRequestFactory
:
<?php
namespace MyLib\Http\Message\Factory;
use MyLib\Http\Message\Uri;
use Psr\Http\Message\UriInterface;
use Psr\Http\Message\StreamInterface;
use MyLib\Http\Message\ServerRequest;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ServerRequestFactoryInterface;
/**
* Server request factory.
*/
class ServerRequestFactory implements ServerRequestFactoryInterface {
/**
* Message body.
*
* @var StreamInterface
*/
protected $body;
/**
* Attributes list.
*
* @var array
*/
protected $attributes = [];
/**
* Headers list with case-insensitive header names.
* A header value can be a string, or an array of strings.
*
* [
* 'header-name 1' => 'header-value',
* 'header-name 2' => [
* 'header-value 1',
* 'header-value 2',
* ],
* ]
*
* @link https://tools.ietf.org/html/rfc7230#section-3.2 Header Fields.
* @link https://tools.ietf.org/html/rfc7231#section-5 Request Header Fields.
*
* @var array
*/
protected $headers = [];
/**
* Parsed body, e.g. the deserialized body parameters, if any.
*
* @var null|array|object
*/
protected $parsedBody;
/**
* Query string arguments.
*
* @var array
*/
protected $queryParams = [];
/**
* Uploaded files.
*
* @var array
*/
protected $uploadedFiles = [];
/**
* Cookies.
*
* @var array
*/
protected $cookieParams = [];
/**
* HTTP protocol version.
*
* @var string
*/
protected $protocolVersion;
/**
*
* @param StreamInterface $body Message body.
* @param array $attributes (optional) Attributes list.
* @param array $headers (optional) Headers list with case-insensitive header names.
* A header value can be a string, or an array of strings.
* @param null|array|object $parsedBody (optional) Parsed body, e.g. the deserialized body
* parameters, if any. The data IS NOT REQUIRED to come from $_POST, but MUST be the
* results of deserializing the request body content.
* @param array $queryParams (optional) Query string arguments. They MAY be injected from
* PHP's $_GET superglobal, or MAY be derived from some other value such as the URI.
* @param array $uploadedFiles (optional) Uploaded files list as a normalized tree of upload
* metadata, with each leaf an instance of Psr\Http\Message\UploadedFileInterface.
* @param array $cookieParams (optional) Cookies. The data IS NOT REQUIRED to come from
* the $_COOKIE superglobal, but MUST be compatible with the structure of $_COOKIE.
* @param string $protocolVersion (optional) HTTP protocol version.
*/
public function __construct(
StreamInterface $body
, array $attributes = []
, array $headers = []
, $parsedBody = null
, array $queryParams = []
, array $uploadedFiles = []
, array $cookieParams = []
, string $protocolVersion = '1.1'
) {
$this->body = $body;
$this->attributes = $attributes;
$this->headers = $headers;
$this->parsedBody = $parsedBody;
$this->queryParams = $queryParams;
$this->uploadedFiles = $uploadedFiles;
$this->cookieParams = $cookieParams;
$this->protocolVersion = $protocolVersion;
}
/**
* Create a new server request.
*
* Note that server-params are taken precisely as given - no parsing/processing
* of the given values is performed, and, in particular, no attempt is made to
* determine the HTTP method or URI, which must be provided explicitly.
*
* @param string $method The HTTP method associated with the request.
* @param UriInterface|string $uri The URI associated with the request. If
* the value is a string, the factory MUST create a UriInterface
* instance based on it.
* @param array $serverParams Array of SAPI parameters with which to seed
* the generated request instance.
*
* @return ServerRequestInterface
*/
public function createServerRequest(
string $method
, $uri
, array $serverParams = []
): ServerRequestInterface {
// Validate method and URI.
$this
->validateMethod($method)
->validateUri($uri)
;
// Create an instance of UriInterface.
if (is_string($uri)) {
$uri = new Uri($uri);
}
// Create the server request.
return new ServerRequest(
$method
, $uri
, $this->body
, $this->attributes
, $this->headers
, $serverParams
, $this->parsedBody
, $this->queryParams
, $this->uploadedFiles
, $this->cookieParams
, $this->protocolVersion
);
}
// [... Other methods ...]
}
通过 ServerRequestFactoryInterface
实现创建 ServerRequestInterface
实例:
<?php
use MyLib\Http\Message\Factory\SapiServerRequestFactory\Apache2HandlerFactory;
// [...]
// Create stream with read-only access.
$body = $streamFactory->createStreamFromFile('php://temp', 'rb');
$serverRequestFactory = new Apache2HandlerFactory(
$body
, [] /* attributes */
, [] /* headers */
, $_POST /* parsed body */
, $_GET /* query params */
, $_FILES /* uploaded files */
, $_COOKIE /* cookie params */
, '1.1' /* http protocol version */
);
$serverRequest = $serverRequestFactory->createServerRequestFromArray($_SERVER);
// [...]