1: | <?php declare(strict_types=1); |
2: | |
3: | namespace Salient\Http\Message; |
4: | |
5: | use Psr\Http\Message\StreamInterface as PsrStreamInterface; |
6: | use Psr\Http\Message\UploadedFileInterface as PsrUploadedFileInterface; |
7: | use Salient\Http\Exception\UploadFailedException; |
8: | use Salient\Http\HttpUtil; |
9: | use Salient\Utility\Exception\InvalidArgumentTypeException; |
10: | use Salient\Utility\File; |
11: | |
12: | |
13: | |
14: | |
15: | class ServerRequestUpload implements PsrUploadedFileInterface |
16: | { |
17: | |
18: | |
19: | |
20: | protected const ERROR_MESSAGE = [ |
21: | \UPLOAD_ERR_OK => 'There is no error, the file uploaded with success', |
22: | \UPLOAD_ERR_INI_SIZE => 'The uploaded file exceeds the upload_max_filesize directive in php.ini', |
23: | \UPLOAD_ERR_FORM_SIZE => 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form', |
24: | \UPLOAD_ERR_PARTIAL => 'The uploaded file was only partially uploaded', |
25: | \UPLOAD_ERR_NO_FILE => 'No file was uploaded', |
26: | \UPLOAD_ERR_NO_TMP_DIR => 'Missing a temporary folder', |
27: | \UPLOAD_ERR_CANT_WRITE => 'Failed to write file to disk', |
28: | \UPLOAD_ERR_EXTENSION => 'A PHP extension stopped the file upload', |
29: | ]; |
30: | |
31: | private PsrStreamInterface $Stream; |
32: | private string $File; |
33: | private ?int $Size; |
34: | private int $Error; |
35: | private ?string $ClientFilename; |
36: | private ?string $ClientMediaType; |
37: | private bool $IsMoved = false; |
38: | |
39: | |
40: | |
41: | |
42: | |
43: | |
44: | public function __construct( |
45: | $resource, |
46: | ?int $size = null, |
47: | int $error = \UPLOAD_ERR_OK, |
48: | ?string $clientFilename = null, |
49: | ?string $clientMediaType = null |
50: | ) { |
51: | $this->Size = $size; |
52: | $this->Error = $error; |
53: | $this->ClientFilename = $clientFilename; |
54: | $this->ClientMediaType = $clientMediaType; |
55: | |
56: | if ($error === \UPLOAD_ERR_OK) { |
57: | if ($resource instanceof PsrStreamInterface) { |
58: | $this->Stream = $resource; |
59: | } elseif (File::isStream($resource)) { |
60: | $this->Stream = new Stream($resource); |
61: | } elseif (is_string($resource)) { |
62: | $this->File = $resource; |
63: | } else { |
64: | throw new InvalidArgumentTypeException( |
65: | 1, |
66: | 'resource', |
67: | PsrStreamInterface::class . '|resource|string', |
68: | $resource, |
69: | ); |
70: | } |
71: | } |
72: | } |
73: | |
74: | |
75: | |
76: | |
77: | public function getStream(): PsrStreamInterface |
78: | { |
79: | $this->assertIsValid(); |
80: | |
81: | return $this->Stream ?? new Stream(File::open($this->File, 'r')); |
82: | } |
83: | |
84: | |
85: | |
86: | |
87: | public function moveTo(string $targetPath): void |
88: | { |
89: | $this->assertIsValid(); |
90: | |
91: | if (isset($this->File)) { |
92: | $result = \PHP_SAPI === 'cli' |
93: | ? @rename($this->File, $targetPath) |
94: | : @move_uploaded_file($this->File, $targetPath); |
95: | if ($result === false) { |
96: | $error = error_get_last(); |
97: | throw new UploadFailedException($error['message'] ?? sprintf( |
98: | 'Error moving %s to %s', |
99: | $this->File, |
100: | $targetPath, |
101: | )); |
102: | } |
103: | } else { |
104: | $target = new Stream(File::open($targetPath, 'w')); |
105: | HttpUtil::copyStream($this->Stream, $target); |
106: | } |
107: | |
108: | $this->IsMoved = true; |
109: | } |
110: | |
111: | |
112: | |
113: | |
114: | public function getSize(): ?int |
115: | { |
116: | return $this->Size; |
117: | } |
118: | |
119: | |
120: | |
121: | |
122: | public function getError(): int |
123: | { |
124: | return $this->Error; |
125: | } |
126: | |
127: | |
128: | |
129: | |
130: | public function getClientFilename(): ?string |
131: | { |
132: | return $this->ClientFilename; |
133: | } |
134: | |
135: | |
136: | |
137: | |
138: | public function getClientMediaType(): ?string |
139: | { |
140: | return $this->ClientMediaType; |
141: | } |
142: | |
143: | private function assertIsValid(): void |
144: | { |
145: | if ($this->Error !== \UPLOAD_ERR_OK) { |
146: | throw new UploadFailedException(sprintf( |
147: | 'Upload failed (%d: %s)', |
148: | $this->Error, |
149: | static::ERROR_MESSAGE[$this->Error] ?? '', |
150: | )); |
151: | } |
152: | |
153: | if ($this->IsMoved) { |
154: | throw new UploadFailedException('Upload already moved'); |
155: | } |
156: | } |
157: | } |
158: | |