| Total Complexity | 43 | 
| Total Lines | 258 | 
| Duplicated Lines | 0 % | 
| Changes | 1 | ||
| Bugs | 0 | Features | 0 | 
Complex classes like UploadedFile often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use UploadedFile, and based on these observations, apply Extract Interface, too.
| 1 | <?php  | 
            ||
| 57 | class UploadedFile implements UploadedFileInterface  | 
            ||
| 58 | { | 
            ||
| 59 | /**  | 
            ||
| 60 | * The uploaded file name  | 
            ||
| 61 | * @var string  | 
            ||
| 62 | */  | 
            ||
| 63 | protected ?string $filename = null;  | 
            ||
| 64 | |||
| 65 | /**  | 
            ||
| 66 | * Whether the uploaded file is moved  | 
            ||
| 67 | * @var bool  | 
            ||
| 68 | */  | 
            ||
| 69 | protected bool $moved = false;  | 
            ||
| 70 | |||
| 71 | /**  | 
            ||
| 72 | * The uploaded file stream  | 
            ||
| 73 | * @var StreamInterface  | 
            ||
| 74 | */  | 
            ||
| 75 | protected ?StreamInterface $stream = null;  | 
            ||
| 76 | |||
| 77 | /**  | 
            ||
| 78 | * The uploaded file size  | 
            ||
| 79 | * @var int|null  | 
            ||
| 80 | */  | 
            ||
| 81 | protected ?int $size;  | 
            ||
| 82 | |||
| 83 | /**  | 
            ||
| 84 | * The uploaded file error  | 
            ||
| 85 | * @var int  | 
            ||
| 86 | */  | 
            ||
| 87 | protected int $error = UPLOAD_ERR_OK;  | 
            ||
| 88 | |||
| 89 | /**  | 
            ||
| 90 | * The uploaded file client name  | 
            ||
| 91 | * @var string|null  | 
            ||
| 92 | */  | 
            ||
| 93 | protected ?string $clientFilename;  | 
            ||
| 94 | |||
| 95 | /**  | 
            ||
| 96 | * The uploaded file client media type  | 
            ||
| 97 | * @var string|null  | 
            ||
| 98 | */  | 
            ||
| 99 | protected ?string $clientMediaType;  | 
            ||
| 100 | |||
| 101 | /**  | 
            ||
| 102 | * Create new uploaded file instance  | 
            ||
| 103 | *  | 
            ||
| 104 | * @param string|StreamInterface $filenameOrStream the filename or stream  | 
            ||
| 105 | * @param int|null $size the upload file size  | 
            ||
| 106 | * @param int $error the upload error code  | 
            ||
| 107 | * @param string|null $clientFilename  | 
            ||
| 108 | * @param string|null $clientMediaType  | 
            ||
| 109 | */  | 
            ||
| 110 | public function __construct(  | 
            ||
| 111 | string|StreamInterface $filenameOrStream,  | 
            ||
| 112 | ?int $size = null,  | 
            ||
| 113 | int $error = UPLOAD_ERR_OK,  | 
            ||
| 114 | ?string $clientFilename = null,  | 
            ||
| 115 | ?string $clientMediaType = null  | 
            ||
| 116 |     ) { | 
            ||
| 117 |         if ($filenameOrStream instanceof StreamInterface) { | 
            ||
| 118 |             if ($filenameOrStream->isReadable() === false) { | 
            ||
| 119 |                 throw new InvalidArgumentException('Stream is not readable'); | 
            ||
| 120 | }  | 
            ||
| 121 | $this->stream = $filenameOrStream;  | 
            ||
| 122 | $this->size = $size ? $size : $filenameOrStream->getSize();  | 
            ||
| 123 |         } else { | 
            ||
| 124 | $this->filename = $filenameOrStream;  | 
            ||
| 125 | $this->size = $size;  | 
            ||
| 126 | }  | 
            ||
| 127 | |||
| 128 | $this->error = $this->filterError($error);  | 
            ||
| 129 | $this->clientFilename = $clientFilename;  | 
            ||
| 130 | $this->clientMediaType = $clientMediaType;  | 
            ||
| 131 | }  | 
            ||
| 132 | |||
| 133 | /**  | 
            ||
| 134 | * Create uploaded file from global variable $_FILES  | 
            ||
| 135 | * @return array<mixed>  | 
            ||
| 136 | */  | 
            ||
| 137 | public static function createFromGlobals(): array  | 
            ||
| 138 |     { | 
            ||
| 139 | return static::normalize($_FILES);  | 
            ||
| 140 | }  | 
            ||
| 141 | |||
| 142 | /**  | 
            ||
| 143 |      * {@inheritdoc} | 
            ||
| 144 | */  | 
            ||
| 145 | public function getStream(): StreamInterface  | 
            ||
| 146 |     { | 
            ||
| 147 |         if ($this->moved) { | 
            ||
| 148 |             throw new RuntimeException('Stream is not avaliable! Uploaded file is moved'); | 
            ||
| 149 | }  | 
            ||
| 150 | |||
| 151 |         if ($this->stream === null && $this->filename !== null) { | 
            ||
| 152 | $this->stream = new Stream($this->filename);  | 
            ||
| 153 | }  | 
            ||
| 154 | |||
| 155 | return $this->stream;  | 
            ||
| 
                                                                                                    
                        
                         | 
                |||
| 156 | }  | 
            ||
| 157 | |||
| 158 | /**  | 
            ||
| 159 |      * {@inheritdoc} | 
            ||
| 160 | */  | 
            ||
| 161 | public function moveTo(string $targetPath): void  | 
            ||
| 162 |     { | 
            ||
| 163 |         if ($this->moved) { | 
            ||
| 164 |             throw new RuntimeException('Uploaded file is already moved'); | 
            ||
| 165 | }  | 
            ||
| 166 | |||
| 167 | $cleanTargetPath = $this->filterTargetPath($targetPath);  | 
            ||
| 168 |         if ($this->filename !== null) { | 
            ||
| 169 |             if (php_sapi_name() === 'cli') { | 
            ||
| 170 |                 if (rename($this->filename, $cleanTargetPath) === false) { | 
            ||
| 171 |                     throw new RuntimeException('Unable to rename the uploaded file'); | 
            ||
| 172 | }  | 
            ||
| 173 |             } else { | 
            ||
| 174 | if (  | 
            ||
| 175 | is_uploaded_file($this->filename) === false || move_uploaded_file(  | 
            ||
| 176 | $this->filename,  | 
            ||
| 177 | $cleanTargetPath  | 
            ||
| 178 | ) === false  | 
            ||
| 179 |                 ) { | 
            ||
| 180 |                     throw new RuntimeException('Unable to move the uploaded file'); | 
            ||
| 181 | }  | 
            ||
| 182 | }  | 
            ||
| 183 |         } else { | 
            ||
| 184 | $stream = $this->getStream();  | 
            ||
| 185 |             if ($stream->isSeekable()) { | 
            ||
| 186 | $stream->rewind();  | 
            ||
| 187 | }  | 
            ||
| 188 | $dest = new Stream($cleanTargetPath);  | 
            ||
| 189 | $bufferSize = 8192;  | 
            ||
| 190 |             while (!$stream->eof()) { | 
            ||
| 191 |                 if (!$dest->write($stream->read($bufferSize))) { | 
            ||
| 192 | break;  | 
            ||
| 193 | }  | 
            ||
| 194 | }  | 
            ||
| 195 | $stream->close();  | 
            ||
| 196 | }  | 
            ||
| 197 | $this->moved = true;  | 
            ||
| 198 | }  | 
            ||
| 199 | |||
| 200 | /**  | 
            ||
| 201 |      * {@inheritdoc} | 
            ||
| 202 | */  | 
            ||
| 203 | public function getSize(): ?int  | 
            ||
| 204 |     { | 
            ||
| 205 | return $this->size;  | 
            ||
| 206 | }  | 
            ||
| 207 | |||
| 208 | /**  | 
            ||
| 209 |      * {@inheritdoc} | 
            ||
| 210 | */  | 
            ||
| 211 | public function getError(): int  | 
            ||
| 212 |     { | 
            ||
| 213 | return $this->error;  | 
            ||
| 214 | }  | 
            ||
| 215 | |||
| 216 | /**  | 
            ||
| 217 |      * {@inheritdoc} | 
            ||
| 218 | */  | 
            ||
| 219 | public function getClientFilename(): ?string  | 
            ||
| 222 | }  | 
            ||
| 223 | |||
| 224 | /**  | 
            ||
| 225 |      * {@inheritdoc} | 
            ||
| 226 | */  | 
            ||
| 227 | public function getClientMediaType(): ?string  | 
            ||
| 228 |     { | 
            ||
| 229 | return $this->clientMediaType;  | 
            ||
| 230 | }  | 
            ||
| 231 | |||
| 232 | /**  | 
            ||
| 233 | * Normalize files according to standard  | 
            ||
| 234 | * @param array<string, array<string, mixed|UploadedFileInterface>> $files  | 
            ||
| 235 | * @return array<string, UploadedFileInterface|UploadedFileInterface[]>  | 
            ||
| 236 | */  | 
            ||
| 237 | public static function normalize(array $files): array  | 
            ||
| 238 |     { | 
            ||
| 239 | $normalized = [];  | 
            ||
| 240 |         foreach ($files as $name => $info) { | 
            ||
| 241 |             if ($info instanceof UploadedFileInterface) { | 
            ||
| 242 | $normalized[$name] = $info;  | 
            ||
| 243 | continue;  | 
            ||
| 244 | }  | 
            ||
| 245 | |||
| 246 |             if (!isset($info['error'])) { | 
            ||
| 247 |                 if (is_array($info)) { | 
            ||
| 248 | $normalized[$name] = static::normalize($info);  | 
            ||
| 249 | }  | 
            ||
| 250 | continue;  | 
            ||
| 251 | }  | 
            ||
| 252 | |||
| 253 | $normalized[$name] = [];  | 
            ||
| 254 |             if (!is_array($info['error'])) { | 
            ||
| 255 | $normalized[$name] = new static(  | 
            ||
| 256 | isset($info['tmp_name']) ? $info['tmp_name'] : '',  | 
            ||
| 257 | !empty($info['size']) ? $info['size'] : null,  | 
            ||
| 258 | $info['error'],  | 
            ||
| 259 | !empty($info['name']) ? $info['name'] : null,  | 
            ||
| 260 | !empty($info['type']) ? $info['type'] : null,  | 
            ||
| 261 | );  | 
            ||
| 262 |             } else { | 
            ||
| 263 | $nestedInfo = [];  | 
            ||
| 264 |                 foreach (array_keys($info['error']) as $key) { | 
            ||
| 265 | $nestedInfo[$key]['tmp_name'] = isset($info['tmp_name'][$key]) ? $info['tmp_name'][$key] : '';  | 
            ||
| 266 | $nestedInfo[$key]['name'] = isset($info['name'][$key]) ? $info['name'][$key] : '';  | 
            ||
| 267 | $nestedInfo[$key]['size'] = isset($info['size'][$key]) ? $info['size'][$key] : null;  | 
            ||
| 268 | $nestedInfo[$key]['error'] = isset($info['error'][$key]) ? $info['error'][$key] : 0;  | 
            ||
| 269 | $nestedInfo[$key]['type'] = isset($info['type'][$key]) ? $info['type'][$key] : '';  | 
            ||
| 270 | |||
| 271 | $normalized[$name] = static::normalize($nestedInfo);  | 
            ||
| 272 | }  | 
            ||
| 273 | }  | 
            ||
| 274 | }  | 
            ||
| 275 | |||
| 276 | return $normalized;  | 
            ||
| 277 | }  | 
            ||
| 278 | |||
| 279 | /**  | 
            ||
| 280 | * Filter the uploaded file error  | 
            ||
| 281 | * @param int $error  | 
            ||
| 282 | * @return int  | 
            ||
| 283 | */  | 
            ||
| 284 | protected function filterError(int $error): int  | 
            ||
| 285 |     { | 
            ||
| 286 | $validErrors = [  | 
            ||
| 287 | UPLOAD_ERR_OK,  | 
            ||
| 288 | UPLOAD_ERR_INI_SIZE,  | 
            ||
| 289 | UPLOAD_ERR_FORM_SIZE,  | 
            ||
| 290 | UPLOAD_ERR_PARTIAL,  | 
            ||
| 291 | UPLOAD_ERR_NO_FILE,  | 
            ||
| 292 | UPLOAD_ERR_NO_TMP_DIR,  | 
            ||
| 293 | UPLOAD_ERR_CANT_WRITE,  | 
            ||
| 294 | UPLOAD_ERR_EXTENSION  | 
            ||
| 295 | ];  | 
            ||
| 296 | |||
| 297 |         if (!in_array($error, $validErrors)) { | 
            ||
| 298 |             throw new InvalidArgumentException('Upload error code must be a PHP file upload error.'); | 
            ||
| 299 | }  | 
            ||
| 300 | |||
| 301 | return $error;  | 
            ||
| 302 | }  | 
            ||
| 303 | |||
| 304 | /**  | 
            ||
| 305 | * Filter the uploaded file target path  | 
            ||
| 306 | * @param string $targetPath  | 
            ||
| 307 | * @return string  | 
            ||
| 308 | */  | 
            ||
| 309 | protected function filterTargetPath(string $targetPath): string  | 
            ||
| 315 | }  | 
            ||
| 316 | }  | 
            ||
| 317 |