Skip to main content

Uploaded files

Dirthara\Http\UploadedFile is a file uploaded with a request. A server request holds them in a tree under getUploadedFiles().

Create an uploaded file​

The uploaded file factory creates one from a stream. To move the original upload rather than copy its stream, pass the source path to the constructor:

use Dirthara\Http\UploadedFile;
use Dirthara\Http\Factory\StreamFactory;

$upload = $_FILES['avatar'];

$file = new UploadedFile(
new StreamFactory()->createStreamFromFile($upload['tmp_name']),
$upload['size'],
$upload['error'],
$upload['name'],
$upload['type'],
$upload['tmp_name'],
);
ParameterTypeDefaultMeaning
stream?StreamInterfaceThe file's contents. Required when the upload succeeded.
size?intnullThe size in bytes, when known.
errorint|UploadErrorUploadError::OkAn UploadError case, or one of PHP's UPLOAD_ERR_* constants.
clientFilename?stringnullThe filename the client sent.
clientMediaType?stringnullThe media type the client sent.
sourcePath?stringnullThe path of the file on disk, used by moveTo().
InputException
An error code that is not an UPLOAD_ERR_* constantInvalidUploadedFileException::invalidError()
A negative sizeInvalidUploadedFileException::invalidSize()
An empty source pathInvalidUploadedFileException::emptySourcePath()
No stream for a successful uploadInvalidUploadedFileException::missingStream()
danger

The client filename and media type come from the client and can be anything. Never use the client filename as a path, and do not trust the media type to describe the contents.

Read the file​

getSize(), getClientFilename(), and getClientMediaType() return what was given, and getError() returns the error code as an int, as PSR-7 requires. getStream() returns the contents. It throws UploadedFileException::uploadFailed() when the upload failed, with a message that says why, and UploadedFileException::alreadyMoved() after moveTo().

Upload errors​

Dirthara\Http\UploadError is an int-backed enum with a case for each of PHP's UPLOAD_ERR_* constants, backed by that constant. Turn the code getError() returns into a case with UploadError::from():

use Dirthara\Http\UploadError;

$error = UploadError::from($file->getError());

if (!$error->isOk()) {
echo $error->description();
}
CaseConstantdescription()
OkUPLOAD_ERR_OKThe file was uploaded successfully.
IniSizeUPLOAD_ERR_INI_SIZEThe file exceeds the upload_max_filesize directive in php.ini.
FormSizeUPLOAD_ERR_FORM_SIZEThe file exceeds the MAX_FILE_SIZE directive in the HTML form.
PartialUPLOAD_ERR_PARTIALThe file was only partially uploaded.
NoFileUPLOAD_ERR_NO_FILENo file was uploaded.
NoTemporaryDirectoryUPLOAD_ERR_NO_TMP_DIRThe temporary folder is missing.
CannotWriteUPLOAD_ERR_CANT_WRITEThe file could not be written to disk.
ExtensionUPLOAD_ERR_EXTENSIONA PHP extension stopped the upload.

isOk() is true only for Ok.

Move the file​

moveTo($targetPath) puts the file at its destination and can only be done once. After it, getStream() and moveTo() throw UploadedFileException::alreadyMoved(), and the stream is closed.

$file->moveTo('/var/uploads/' . bin2hex(random_bytes(16)));

It tries each way in turn until one applies:

  1. A source path PHP registered as an upload is moved with move_uploaded_file().
  2. Another source path that is a file is renamed.
  3. Otherwise the stream is copied to the target, and the source file, when there is one, is removed afterwards.
SituationException
An empty target pathInvalidUploadedFileException::emptyTargetPath()
A target path with a null byteInvalidUploadedFileException::targetPathContainsNullByte()
The upload failedUploadedFileException::uploadFailed()
The file was already movedUploadedFileException::alreadyMoved()
move_uploaded_file() failedUploadedFileException::unableToMove()
The stream cannot be read, for example because it was detachedUploadedFileException::unableToReadStream()
The target cannot be opened for writingUploadedFileException::unableToOpenTarget()
The stream stopped producing data, or raised a RuntimeExceptionUploadedFileException::unableToReadStream(), with the stream's exception as the previous one
Writing the target failedUploadedFileException::unableToWrite()
The source could not be removed after copyingUploadedFileException::unableToRemoveSource()

When copying fails, the partly written target is removed, and the exception's context holds the targetPath. An exception the stream throws that already belongs to this package, such as an InvalidStreamException, is rethrown with the targetPath added rather than wrapped. A wrapped exception's message is not copied, since it can contain anything; when its code is a string SQLSTATE, as a PDOException reports, the context holds it under sqlState.