Chris@0: 'There is no error, the file uploaded with success', Chris@12: UPLOAD_ERR_INI_SIZE => 'The uploaded file exceeds the upload_max_filesize directive in php.ini', Chris@12: UPLOAD_ERR_FORM_SIZE => 'The uploaded file exceeds the MAX_FILE_SIZE directive that was ' Chris@12: . 'specified in the HTML form', Chris@12: UPLOAD_ERR_PARTIAL => 'The uploaded file was only partially uploaded', Chris@12: UPLOAD_ERR_NO_FILE => 'No file was uploaded', Chris@12: UPLOAD_ERR_NO_TMP_DIR => 'Missing a temporary folder', Chris@12: UPLOAD_ERR_CANT_WRITE => 'Failed to write file to disk', Chris@12: UPLOAD_ERR_EXTENSION => 'A PHP extension stopped the file upload.', Chris@12: ]; Chris@12: Chris@0: /** Chris@12: * @var string|null Chris@0: */ Chris@0: private $clientFilename; Chris@0: Chris@0: /** Chris@12: * @var string|null Chris@0: */ Chris@0: private $clientMediaType; Chris@0: Chris@0: /** Chris@0: * @var int Chris@0: */ Chris@0: private $error; Chris@0: Chris@0: /** Chris@0: * @var null|string Chris@0: */ Chris@0: private $file; Chris@0: Chris@0: /** Chris@0: * @var bool Chris@0: */ Chris@0: private $moved = false; Chris@0: Chris@0: /** Chris@0: * @var int Chris@0: */ Chris@0: private $size; Chris@0: Chris@0: /** Chris@0: * @var null|StreamInterface Chris@0: */ Chris@0: private $stream; Chris@0: Chris@0: /** Chris@0: * @param string|resource $streamOrFile Chris@0: * @param int $size Chris@0: * @param int $errorStatus Chris@0: * @param string|null $clientFilename Chris@0: * @param string|null $clientMediaType Chris@0: * @throws InvalidArgumentException Chris@0: */ Chris@0: public function __construct($streamOrFile, $size, $errorStatus, $clientFilename = null, $clientMediaType = null) Chris@0: { Chris@0: if ($errorStatus === UPLOAD_ERR_OK) { Chris@0: if (is_string($streamOrFile)) { Chris@0: $this->file = $streamOrFile; Chris@0: } Chris@0: if (is_resource($streamOrFile)) { Chris@0: $this->stream = new Stream($streamOrFile); Chris@0: } Chris@0: Chris@0: if (! $this->file && ! $this->stream) { Chris@0: if (! $streamOrFile instanceof StreamInterface) { Chris@0: throw new InvalidArgumentException('Invalid stream or file provided for UploadedFile'); Chris@0: } Chris@0: $this->stream = $streamOrFile; Chris@0: } Chris@0: } Chris@0: Chris@0: if (! is_int($size)) { Chris@0: throw new InvalidArgumentException('Invalid size provided for UploadedFile; must be an int'); Chris@0: } Chris@0: $this->size = $size; Chris@0: Chris@0: if (! is_int($errorStatus) Chris@0: || 0 > $errorStatus Chris@0: || 8 < $errorStatus Chris@0: ) { Chris@0: throw new InvalidArgumentException( Chris@0: 'Invalid error status for UploadedFile; must be an UPLOAD_ERR_* constant' Chris@0: ); Chris@0: } Chris@0: $this->error = $errorStatus; Chris@0: Chris@0: if (null !== $clientFilename && ! is_string($clientFilename)) { Chris@0: throw new InvalidArgumentException( Chris@0: 'Invalid client filename provided for UploadedFile; must be null or a string' Chris@0: ); Chris@0: } Chris@0: $this->clientFilename = $clientFilename; Chris@0: Chris@0: if (null !== $clientMediaType && ! is_string($clientMediaType)) { Chris@0: throw new InvalidArgumentException( Chris@0: 'Invalid client media type provided for UploadedFile; must be null or a string' Chris@0: ); Chris@0: } Chris@0: $this->clientMediaType = $clientMediaType; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: * @throws \RuntimeException if the upload was not successful. Chris@0: */ Chris@0: public function getStream() Chris@0: { Chris@0: if ($this->error !== UPLOAD_ERR_OK) { Chris@12: throw new RuntimeException(sprintf( Chris@12: 'Cannot retrieve stream due to upload error: %s', Chris@12: self::ERROR_MESSAGES[$this->error] Chris@12: )); Chris@0: } Chris@0: Chris@0: if ($this->moved) { Chris@0: throw new RuntimeException('Cannot retrieve stream after it has already been moved'); Chris@0: } Chris@0: Chris@0: if ($this->stream instanceof StreamInterface) { Chris@0: return $this->stream; Chris@0: } Chris@0: Chris@0: $this->stream = new Stream($this->file); Chris@0: return $this->stream; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: * Chris@0: * @see http://php.net/is_uploaded_file Chris@0: * @see http://php.net/move_uploaded_file Chris@0: * @param string $targetPath Path to which to move the uploaded file. Chris@0: * @throws \RuntimeException if the upload was not successful. Chris@0: * @throws \InvalidArgumentException if the $path specified is invalid. Chris@0: * @throws \RuntimeException on any error during the move operation, or on Chris@0: * the second or subsequent call to the method. Chris@0: */ Chris@0: public function moveTo($targetPath) Chris@0: { Chris@0: if ($this->moved) { Chris@0: throw new RuntimeException('Cannot move file; already moved!'); Chris@0: } Chris@0: Chris@0: if ($this->error !== UPLOAD_ERR_OK) { Chris@12: throw new RuntimeException(sprintf( Chris@12: 'Cannot retrieve stream due to upload error: %s', Chris@12: self::ERROR_MESSAGES[$this->error] Chris@12: )); Chris@0: } Chris@0: Chris@0: if (! is_string($targetPath) || empty($targetPath)) { Chris@0: throw new InvalidArgumentException( Chris@0: 'Invalid path provided for move operation; must be a non-empty string' Chris@0: ); Chris@0: } Chris@0: Chris@0: $targetDirectory = dirname($targetPath); Chris@0: if (! is_dir($targetDirectory) || ! is_writable($targetDirectory)) { Chris@0: throw new RuntimeException(sprintf( Chris@0: 'The target directory `%s` does not exists or is not writable', Chris@0: $targetDirectory Chris@0: )); Chris@0: } Chris@0: Chris@0: $sapi = PHP_SAPI; Chris@0: switch (true) { Chris@0: case (empty($sapi) || 0 === strpos($sapi, 'cli') || ! $this->file): Chris@0: // Non-SAPI environment, or no filename present Chris@0: $this->writeFile($targetPath); Chris@0: break; Chris@0: default: Chris@0: // SAPI environment, with file present Chris@0: if (false === move_uploaded_file($this->file, $targetPath)) { Chris@0: throw new RuntimeException('Error occurred while moving uploaded file'); Chris@0: } Chris@0: break; Chris@0: } Chris@0: Chris@0: $this->moved = true; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: * Chris@0: * @return int|null The file size in bytes or null if unknown. Chris@0: */ Chris@0: public function getSize() Chris@0: { Chris@0: return $this->size; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: * Chris@0: * @see http://php.net/manual/en/features.file-upload.errors.php Chris@0: * @return int One of PHP's UPLOAD_ERR_XXX constants. Chris@0: */ Chris@0: public function getError() Chris@0: { Chris@0: return $this->error; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: * Chris@0: * @return string|null The filename sent by the client or null if none Chris@0: * was provided. Chris@0: */ Chris@0: public function getClientFilename() Chris@0: { Chris@0: return $this->clientFilename; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getClientMediaType() Chris@0: { Chris@0: return $this->clientMediaType; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Write internal stream to given path Chris@0: * Chris@0: * @param string $path Chris@0: */ Chris@0: private function writeFile($path) Chris@0: { Chris@0: $handle = fopen($path, 'wb+'); Chris@0: if (false === $handle) { Chris@0: throw new RuntimeException('Unable to write to designated path'); Chris@0: } Chris@0: Chris@0: $stream = $this->getStream(); Chris@0: $stream->rewind(); Chris@0: while (! $stream->eof()) { Chris@0: fwrite($handle, $stream->read(4096)); Chris@0: } Chris@0: Chris@0: fclose($handle); Chris@0: } Chris@0: }