These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | declare(strict_types=1); |
||
4 | |||
5 | namespace Nyholm\Psr7; |
||
6 | |||
7 | use InvalidArgumentException; |
||
8 | use Nyholm\Psr7\Factory\StreamFactory; |
||
9 | use Psr\Http\Message\StreamInterface; |
||
10 | use Psr\Http\Message\UploadedFileInterface; |
||
11 | use RuntimeException; |
||
12 | |||
13 | /** |
||
14 | * @author Michael Dowling and contributors to guzzlehttp/psr7 |
||
15 | * @author Tobias Nyholm <[email protected]> |
||
16 | */ |
||
17 | class UploadedFile implements UploadedFileInterface |
||
18 | { |
||
19 | /** @var int[] */ |
||
20 | private static $errors = [ |
||
21 | UPLOAD_ERR_OK, UPLOAD_ERR_INI_SIZE, UPLOAD_ERR_FORM_SIZE, UPLOAD_ERR_PARTIAL, UPLOAD_ERR_NO_FILE, |
||
22 | UPLOAD_ERR_NO_TMP_DIR, UPLOAD_ERR_CANT_WRITE, UPLOAD_ERR_EXTENSION, |
||
23 | ]; |
||
24 | |||
25 | /** @var string */ |
||
26 | private $clientFilename; |
||
27 | |||
28 | /** @var string */ |
||
29 | private $clientMediaType; |
||
30 | |||
31 | /** @var int */ |
||
32 | private $error; |
||
33 | |||
34 | /** @var null|string */ |
||
35 | private $file; |
||
36 | |||
37 | /** @var bool */ |
||
38 | private $moved = false; |
||
39 | |||
40 | /** @var int */ |
||
41 | private $size; |
||
42 | |||
43 | /** @var StreamInterface|null */ |
||
44 | private $stream; |
||
45 | |||
46 | /** |
||
47 | * @param StreamInterface|string|resource $streamOrFile |
||
48 | * @param int $size |
||
49 | * @param int $errorStatus |
||
50 | * @param string|null $clientFilename |
||
51 | * @param string|null $clientMediaType |
||
52 | */ |
||
53 | 70 | public function __construct( |
|
54 | $streamOrFile, |
||
55 | $size, |
||
56 | $errorStatus, |
||
57 | $clientFilename = null, |
||
58 | $clientMediaType = null |
||
59 | ) { |
||
60 | 70 | $this->setError($errorStatus); |
|
61 | 61 | $this->setSize($size); |
|
62 | 57 | $this->setClientFilename($clientFilename); |
|
63 | 51 | $this->setClientMediaType($clientMediaType); |
|
64 | |||
65 | 45 | if ($this->isOk()) { |
|
66 | 23 | $this->setStreamOrFile($streamOrFile); |
|
67 | } |
||
68 | 38 | } |
|
69 | |||
70 | /** |
||
71 | * Depending on the value set file or stream variable. |
||
72 | * |
||
73 | * @param mixed $streamOrFile |
||
74 | * |
||
75 | * @throws InvalidArgumentException |
||
76 | */ |
||
77 | 23 | private function setStreamOrFile($streamOrFile) |
|
78 | { |
||
79 | 23 | if (is_string($streamOrFile)) { |
|
80 | 2 | $this->file = $streamOrFile; |
|
81 | 21 | } elseif (is_resource($streamOrFile)) { |
|
82 | 2 | $this->stream = Stream::createFromResource($streamOrFile); |
|
83 | 19 | } elseif ($streamOrFile instanceof StreamInterface) { |
|
84 | 12 | $this->stream = $streamOrFile; |
|
85 | } else { |
||
86 | 7 | throw new InvalidArgumentException('Invalid stream or file provided for UploadedFile'); |
|
87 | } |
||
88 | 16 | } |
|
89 | |||
90 | /** |
||
91 | * @param int $error |
||
92 | * |
||
93 | * @throws InvalidArgumentException |
||
94 | */ |
||
95 | 70 | private function setError($error) |
|
96 | { |
||
97 | 70 | if (false === is_int($error)) { |
|
98 | 7 | throw new InvalidArgumentException('Upload file error status must be an integer'); |
|
99 | } |
||
100 | |||
101 | 63 | if (false === in_array($error, self::$errors)) { |
|
102 | 2 | throw new InvalidArgumentException('Invalid error status for UploadedFile'); |
|
103 | } |
||
104 | |||
105 | 61 | $this->error = $error; |
|
106 | 61 | } |
|
107 | |||
108 | /** |
||
109 | * @param int $size |
||
110 | * |
||
111 | * @throws InvalidArgumentException |
||
112 | */ |
||
113 | 61 | private function setSize($size) |
|
114 | { |
||
115 | 61 | if (false === is_int($size)) { |
|
116 | 4 | throw new InvalidArgumentException('Upload file size must be an integer'); |
|
117 | } |
||
118 | |||
119 | 57 | $this->size = $size; |
|
120 | 57 | } |
|
121 | |||
122 | /** |
||
123 | * @param mixed $param |
||
124 | * |
||
125 | * @return bool |
||
126 | */ |
||
127 | 57 | private function isStringOrNull($param) |
|
128 | { |
||
129 | 57 | return in_array(gettype($param), ['string', 'NULL']); |
|
130 | } |
||
131 | |||
132 | /** |
||
133 | * @param mixed $param |
||
134 | * |
||
135 | * @return bool |
||
136 | */ |
||
137 | 12 | private function isStringNotEmpty($param) |
|
138 | { |
||
139 | 12 | return is_string($param) && false === empty($param); |
|
140 | } |
||
141 | |||
142 | /** |
||
143 | * @param string|null $clientFilename |
||
144 | * |
||
145 | * @throws InvalidArgumentException |
||
146 | */ |
||
147 | 57 | private function setClientFilename($clientFilename) |
|
148 | { |
||
149 | 57 | if (false === $this->isStringOrNull($clientFilename)) { |
|
150 | 6 | throw new InvalidArgumentException('Upload file client filename must be a string or null'); |
|
151 | } |
||
152 | |||
153 | 51 | $this->clientFilename = $clientFilename; |
|
154 | 51 | } |
|
155 | |||
156 | /** |
||
157 | * @param string|null $clientMediaType |
||
158 | * |
||
159 | * @throws InvalidArgumentException |
||
160 | */ |
||
161 | 51 | private function setClientMediaType($clientMediaType) |
|
162 | { |
||
163 | 51 | if (false === $this->isStringOrNull($clientMediaType)) { |
|
164 | 6 | throw new InvalidArgumentException('Upload file client media type must be a string or null'); |
|
165 | } |
||
166 | |||
167 | 45 | $this->clientMediaType = $clientMediaType; |
|
168 | 45 | } |
|
169 | |||
170 | /** |
||
171 | * @return bool Return true if there is no upload error. |
||
172 | */ |
||
173 | 45 | private function isOk() |
|
174 | { |
||
175 | 45 | return UPLOAD_ERR_OK === $this->error; |
|
176 | } |
||
177 | |||
178 | /** |
||
179 | * @throws RuntimeException if is moved or not ok |
||
180 | */ |
||
181 | 30 | private function validateActive() |
|
182 | { |
||
183 | 30 | if (false === $this->isOk()) { |
|
184 | 14 | throw new RuntimeException('Cannot retrieve stream due to upload error'); |
|
185 | } |
||
186 | |||
187 | 16 | if ($this->moved) { |
|
188 | 2 | throw new RuntimeException('Cannot retrieve stream after it has already been moved'); |
|
189 | } |
||
190 | 16 | } |
|
191 | |||
192 | 14 | public function getStream() |
|
193 | { |
||
194 | 14 | $this->validateActive(); |
|
195 | |||
196 | 7 | if ($this->stream instanceof StreamInterface) { |
|
197 | 6 | return $this->stream; |
|
198 | } |
||
199 | |||
200 | 1 | $resource = fopen($this->file, 'r'); |
|
201 | |||
202 | 1 | return Stream::createFromResource($resource); |
|
0 ignored issues
–
show
|
|||
203 | } |
||
204 | |||
205 | 19 | public function moveTo($targetPath) |
|
206 | { |
||
207 | 19 | $this->validateActive(); |
|
208 | |||
209 | 12 | if (false === $this->isStringNotEmpty($targetPath)) { |
|
210 | 8 | throw new InvalidArgumentException('Invalid path provided for move operation; must be a non-empty string'); |
|
211 | } |
||
212 | |||
213 | 4 | if (null !== $this->file) { |
|
214 | 1 | $this->moved = 'cli' == php_sapi_name() |
|
215 | 1 | ? rename($this->file, $targetPath) |
|
216 | 1 | : move_uploaded_file($this->file, $targetPath); |
|
217 | } else { |
||
218 | 3 | $stream = $this->getStream(); |
|
219 | 3 | if ($stream->isSeekable()) { |
|
220 | 3 | $stream->rewind(); |
|
221 | } |
||
222 | 3 | (new StreamFactory())->copyToStream( |
|
223 | 3 | $stream, |
|
224 | 3 | Stream::createFromResource(fopen($targetPath, 'w')) |
|
225 | ); |
||
226 | |||
227 | 3 | $this->moved = true; |
|
228 | } |
||
229 | |||
230 | 4 | if (false === $this->moved) { |
|
231 | throw new RuntimeException(sprintf('Uploaded file could not be moved to %s', $targetPath)); |
||
232 | } |
||
233 | 4 | } |
|
234 | |||
235 | 3 | public function getSize() |
|
236 | { |
||
237 | 3 | return $this->size; |
|
238 | } |
||
239 | |||
240 | 10 | public function getError() |
|
241 | { |
||
242 | 10 | return $this->error; |
|
243 | } |
||
244 | |||
245 | 3 | public function getClientFilename() |
|
246 | { |
||
247 | 3 | return $this->clientFilename; |
|
248 | } |
||
249 | |||
250 | 3 | public function getClientMediaType() |
|
251 | { |
||
252 | 3 | return $this->clientMediaType; |
|
253 | } |
||
254 | } |
||
255 |
If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.
Let’s take a look at an example:
Our function
my_function
expects aPost
object, and outputs the author of the post. The base classPost
returns a simple string and outputting a simple string will work just fine. However, the child classBlogPost
which is a sub-type ofPost
instead decided to return anobject
, and is therefore violating the SOLID principles. If aBlogPost
were passed tomy_function
, PHP would not complain, but ultimately fail when executing thestrtoupper
call in its body.