index/src/Http/HttpUploadedFile.php

189 lines
5 KiB
PHP

<?php
// HttpUploadedFile.php
// Created: 2022-02-10
// Updated: 2022-02-27
namespace Index\Http;
use Index\MediaType;
use Index\ICloseable;
use Index\IO\Stream;
use Index\IO\FileStream;
use InvalidArgumentException;
use RuntimeException;
class HttpUploadedFile implements ICloseable {
private int $errorCode;
private int $size;
private string $localFileName;
private string $suggestedFileName;
private MediaType $suggestedMediaType;
private bool $hasMoved = false;
private ?Stream $stream = null;
public function __construct(
int $errorCode,
int $size,
string $localFileName,
string $suggestedFileName,
MediaType|string $suggestedMediaType
) {
if(!($suggestedMediaType instanceof MediaType))
$suggestedMediaType = MediaType::parse($suggestedMediaType);
$this->errorCode = $errorCode;
$this->size = $size;
$this->localFileName = $localFileName;
$this->suggestedFileName = $suggestedFileName;
$this->suggestedMediaType = $suggestedMediaType;
}
public function getErrorCode(): int {
return $this->errorCode;
}
public function getSize(): int {
return $this->size;
}
public function getLocalFileName(): ?string {
return $this->localFileName;
}
public function getLocalMediaType(): ?MediaType {
return MediaType::fromPath($this->localFileName);
}
public function getSuggestedFileName(): string {
return $this->suggestedFileName;
}
public function getSuggestedMediaType(): MediaType {
return $this->suggestedMediaType;
}
public function hasMoved(): bool {
return $this->hasMoved;
}
public function getStream(): Stream {
if($this->stream === null) {
if($this->errorCode !== UPLOAD_ERR_OK)
throw new RuntimeException('Can\'t open stream because of an upload error.');
if($this->hasMoved)
throw new RuntimeException('Can\'t open stream because file has already been moved.');
$this->stream = FileStream::openRead($this->localFileName);
}
return $this->stream;
}
public function moveTo(string $path): void {
if($this->hasMoved)
throw new RuntimeException('This uploaded file has already been moved.');
if($this->errorCode !== UPLOAD_ERR_OK)
throw new RuntimeException('Can\'t move file because of an upload error.');
if(empty($path))
throw new InvalidArgumentException('$path is not a valid path.');
if($this->stream !== null)
$this->stream->close();
$this->hasMoved = PHP_SAPI === 'CLI'
? rename($this->localFileName, $path)
: move_uploaded_file($this->localFileName, $path);
if(!$this->hasMoved)
throw new RuntimeException('Failed to move file to ' . $path);
$this->localFileName = $path;
}
public function close(): void {
if($this->stream !== null)
$this->stream->close();
}
public function __destruct() {
$this->close();
}
public static function createFromFILE(array $file): self {
return new HttpUploadedFile(
$file['error'] ?? UPLOAD_ERR_NO_FILE,
$file['size'] ?? -1,
$file['tmp_name'] ?? '',
$file['name'] ?? '',
$file['type'] ?? ''
);
}
public static function createFromFILES(array $files): array {
if(empty($files))
return [];
return self::createObjectInstances(self::normalizeFILES($files));
}
private static function traverseFILES(array $files, string $keyName): array {
$arr = [];
foreach($files as $key => $val) {
$key = "_{$key}";
if(is_array($val)) {
$arr[$key] = self::traverseFILES($val, $keyName);
} else {
$arr[$key][$keyName] = $val;
}
}
return $arr;
}
private static function normalizeFILES(array $files): array {
$out = [];
foreach($files as $key => $arr) {
if(empty($arr))
continue;
$key = '_' . $key;
if(is_int($arr['error'])) {
$out[$key] = $arr;
continue;
}
if(is_array($arr['error'])) {
$keys = array_keys($arr);
foreach($keys as $keyName) {
$out[$key] = array_merge_recursive($out[$key] ?? [], self::traverseFILES($arr[$keyName], $keyName));
}
continue;
}
}
return $out;
}
private static function createObjectInstances(array $files): array {
$coll = [];
foreach($files as $key => $val) {
$key = substr($key, 1);
if(isset($val['error'])) {
$coll[$key] = self::createFromFILE($val);
} else {
$coll[$key] = self::createObjectInstances($val);
}
}
return $coll;
}
}