简短版本:
当用户使用表单上传文件时,数组将保存在全局变量$_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
全局变量。对于规范化过程,我需要检查以下标准文件上传密钥之一的存在性和“正确性”(可能是单词的错误选择):
name
type
tmp_name
error
size
原则上,如果在提供的上载文件列表(也可以是多维数组)中找到选定的密钥(我现在选择tmp_name
),则可以认为密钥所属的阵列项目是标准文件上传阵列项目,其中包含上述密钥列表。否则,例如如果找不到所选的键,则认为相应的数组项是UploadedFileInterface的实例。
不幸的是,在上传标准文件的情况下,我无法找到关于哪个键(来自上面的列表)始终存在的可靠信息,并且(也许)始终在$_FILES
变量中设置,没有无论其他列表键的外观如何,或使用哪种浏览器。
如果您能在此事上为我提供帮助,我将不胜感激。
谢谢。
答案 0 :(得分:1)
我决定使用tmp_name
键进行文件上传验证。
很不幸,我很久以前就做出了这个决定。因此,我再也记不清所有支持它的参数了,这些参数是由我阅读的文档和进行的测试得出的。不过,其中一个论点是,与其他键相比,tmp_name
键的值无法在客户端设置/更改。应用程序运行的环境决定应为其设置哪个值。
我将在此处发布我当时写的PSR-7和PSR-17实现的最终版本(关于上传的文件)。也许对某人会有帮助。
ServerRequestFactoryInterface
的实现:
它读取上载文件的列表(在$_FILES
中找到,或手动作为参数传递),如果尚未完成,则将其转换为“上载元数据的规范化树,每个叶子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);
}
}
基类ServerRequestFactory
:
<?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 ...]
}
通过ServerRequestInterface
实现创建ServerRequestFactoryInterface
实例:
<?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);
// [...]