Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
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 |
||
32 | class UploadedFile implements \ArrayAccess |
||
33 | { |
||
34 | /** |
||
35 | * @var string The name of the file. |
||
36 | */ |
||
37 | protected $name; |
||
38 | |||
39 | /** |
||
40 | * @var string The type of the file. |
||
41 | */ |
||
42 | protected $type; |
||
43 | |||
44 | /** |
||
45 | * @var int The size of the file, in bytes. |
||
46 | */ |
||
47 | protected $size; |
||
48 | |||
49 | /** |
||
50 | * @var string A local path name to the file, if persisted to disk. |
||
51 | */ |
||
52 | protected $tmpName; |
||
53 | |||
54 | /** |
||
55 | * @var int An UPLOAD_ERR_* error code for the file upload. |
||
56 | */ |
||
57 | protected $error; |
||
58 | |||
59 | /** |
||
60 | * @var bool Whether or not this was a file uploaded via an HTML form. |
||
61 | */ |
||
62 | protected $isUploadedFile; |
||
63 | |||
64 | /** |
||
65 | * @var bool Whether or not this file has been moved already. |
||
66 | */ |
||
67 | protected $isMoved; |
||
68 | |||
69 | /** |
||
70 | * @var string A string of the raw binary contents of the file. |
||
71 | */ |
||
72 | protected $contents; |
||
73 | |||
74 | /** |
||
75 | * @var resource A resource pointer to the stream with the contents. |
||
76 | */ |
||
77 | protected $stream; |
||
78 | |||
79 | /** |
||
80 | * @var array An array to map get* method name fragments to indices. |
||
81 | */ |
||
82 | protected static $indexMap = array( |
||
83 | 'name' => 'name', |
||
84 | 'type' => 'type', |
||
85 | 'size' => 'size', |
||
86 | 'tmp_name' => 'tmpName', |
||
87 | 'error' => 'error', |
||
88 | 'is_uploaded_file' => 'isUploadedFile', |
||
89 | 'is_moved' => 'isMoved', |
||
90 | 'contents' => 'contents', |
||
91 | 'stream' => 'stream', |
||
92 | ); |
||
93 | |||
94 | /** |
||
95 | * Constructor. |
||
96 | * |
||
97 | * @param array The fields for this file. |
||
98 | * |
||
99 | * @see ArrayObject::__construct() |
||
100 | * |
||
101 | * @author David Zülke <[email protected]> |
||
102 | * @since 0.11.0 |
||
103 | */ |
||
104 | public function __construct(array $array) |
||
105 | { |
||
106 | $defaults = array( |
||
107 | 'name' => null, |
||
108 | 'type' => null, |
||
109 | 'size' => -1, |
||
110 | 'tmp_name' => null, |
||
111 | 'error' => UPLOAD_ERR_OK, |
||
112 | 'is_uploaded_file' => false, |
||
113 | 'contents' => null, |
||
114 | 'stream' => null, |
||
115 | ); |
||
116 | $array = array_merge($defaults, $array, array('is_moved' => false)); // make sure it's marked not moved by default |
||
117 | |||
118 | // we need exactly one of tmp_name, contents or stream |
||
119 | if (isset($array['tmp_name'], $array['contents'], $array['stream']) || |
||
120 | isset($array['tmp_name'], $array['contents']) || |
||
121 | isset($array['tmp_name'], $array['stream']) || |
||
122 | isset($array['contents'], $array['stream']) || |
||
123 | (!isset($array['tmp_name']) && !isset($array['contents']) && !isset($array['stream'])) |
||
124 | ) { |
||
125 | throw new InvalidArgumentException('Value for exactly one of keys "tmp_name", "contents" or "stream" must be supplied.'); |
||
126 | } |
||
127 | |||
128 | // fill local props |
||
129 | foreach (self::$indexMap as $index => $property) { |
||
130 | if (isset($array[$index])) { |
||
131 | $this->$property = $array[$index]; |
||
132 | } |
||
133 | } |
||
134 | } |
||
135 | |||
136 | /** |
||
137 | * Property access overload. |
||
138 | * |
||
139 | * @param string The key to fetch. |
||
140 | * |
||
141 | * @return mixed The value of the key or null. |
||
142 | * |
||
143 | * @author David Zülke <[email protected]> |
||
144 | * @since 1.1.0 |
||
145 | */ |
||
146 | public function __get($key) |
||
153 | |||
154 | /** |
||
155 | * Property isset overload. |
||
156 | * |
||
157 | * @param string The key to check. |
||
158 | * |
||
159 | * @return bool Whether the given key exists. |
||
160 | * |
||
161 | * @author David Zülke <[email protected]> |
||
162 | * @since 1.1.0 |
||
163 | */ |
||
164 | public function __isset($key) |
||
169 | |||
170 | /** |
||
171 | * Array access: existence check. |
||
172 | * |
||
173 | * @param string The key to check. |
||
174 | * |
||
175 | * @return bool Whether or not the key exists. |
||
176 | * |
||
177 | * @author David Zülke <[email protected]> |
||
178 | * @since 1.1.0 |
||
179 | */ |
||
180 | public function offsetExists($key) |
||
188 | |||
189 | /** |
||
190 | * Array access: fetch value. |
||
191 | * |
||
192 | * @param string The key of the value to fetch. |
||
193 | * |
||
194 | * @return mixed The key for the given value. |
||
195 | * |
||
196 | * @author David Zülke <[email protected]> |
||
197 | * @since 1.1.0 |
||
198 | */ |
||
199 | public function offsetGet($key) |
||
206 | |||
207 | /** |
||
208 | * Array access: set value. |
||
209 | * |
||
210 | * @param string The key to set. |
||
211 | * @param string The value to set. |
||
212 | * |
||
213 | * @throws BadMethodCallException AgaviUploadedFile objects are immutable. |
||
214 | * |
||
215 | * @author David Zülke <[email protected]> |
||
216 | * @since 1.1.0 |
||
217 | */ |
||
218 | public function offsetSet($key, $value) |
||
222 | |||
223 | /** |
||
224 | * Array access: unset value. |
||
225 | * |
||
226 | * @param string The key to unset. |
||
227 | * |
||
228 | * @throws BadMethodCallException AgaviUploadedFile objects are immutable. |
||
229 | * |
||
230 | * @author David Zülke <[email protected]> |
||
231 | * @since 1.1.0 |
||
232 | */ |
||
233 | public function offsetUnset($key) |
||
237 | |||
238 | /** |
||
239 | * Get the name of the file as submitted by the client. |
||
240 | * |
||
241 | * @return string The file name as submitted by the client. |
||
242 | * |
||
243 | * @author David Zülke <[email protected]> |
||
244 | * @since 1.1.0 |
||
245 | */ |
||
246 | public function getName() |
||
250 | |||
251 | /** |
||
252 | * Get the type of the file as submitted by the client. |
||
253 | * |
||
254 | * @return string The file type as submitted by the client. |
||
255 | * |
||
256 | * @author David Zülke <[email protected]> |
||
257 | * @since 1.1.0 |
||
258 | */ |
||
259 | public function getType() |
||
263 | |||
264 | /** |
||
265 | * Get the size of the file. |
||
266 | * |
||
267 | * @return int The length of the file in bytes. |
||
268 | * |
||
269 | * @author David Zülke <[email protected]> |
||
270 | * @since 1.1.0 |
||
271 | */ |
||
272 | public function getSize() |
||
276 | |||
277 | /** |
||
278 | * Get the temporary filename of this file. |
||
279 | * |
||
280 | * @return string The temporary filename for this file. |
||
281 | * |
||
282 | * @author David Zülke <[email protected]> |
||
283 | * @since 1.1.0 |
||
284 | */ |
||
285 | public function getTmpName() |
||
304 | |||
305 | /** |
||
306 | * Check if this uploaded file has a temporary filename. |
||
307 | * If a file has no temp name, then it means that this object was constructed |
||
308 | * internally using the file contents rather than by PHP's upload handler. |
||
309 | * On calling getTmpName(), the contents will be flushed to disk so access |
||
310 | * using file methods is possible. |
||
311 | * |
||
312 | * @return bool Whether or not the file contents are on disk yet. |
||
313 | * |
||
314 | * @author David Zülke <[email protected]> |
||
315 | * @since 1.1.0 |
||
316 | */ |
||
317 | public function hasTmpName() |
||
321 | |||
322 | /** |
||
323 | * Get the error code for this uploaded file. |
||
324 | * |
||
325 | * @return int One of PHP's UPLOAD_ERR_* constants. |
||
326 | * |
||
327 | * @author David Zülke <[email protected]> |
||
328 | * @since 1.1.0 |
||
329 | */ |
||
330 | public function getError() |
||
334 | |||
335 | /** |
||
336 | * Check if this file is a multipart/form-data upload handled by PHP itself, |
||
337 | * or another type of file submission handled by Agavi. |
||
338 | * |
||
339 | * @return bool Whether or not this file was uploaded through a web form. |
||
340 | * |
||
341 | * @author David Zülke <[email protected]> |
||
342 | * @since 1.1.0 |
||
343 | */ |
||
344 | public function getIsUploadedFile() |
||
348 | |||
349 | /** |
||
350 | * Check if this file has been moved from it's temporary location. |
||
351 | * |
||
352 | * @return bool Whether or not the temporary file has been moved yet. |
||
353 | * |
||
354 | * @author David Zülke <[email protected]> |
||
355 | * @since 1.1.0 |
||
356 | */ |
||
357 | public function getIsMoved() |
||
361 | |||
362 | /** |
||
363 | * Check whether or not this file has an error. |
||
364 | * |
||
365 | * This only returns PHP's own information, not validator's. |
||
366 | * |
||
367 | * @return bool True in case of UPLOAD_ERR_OK, false otherwise. |
||
368 | * |
||
369 | * @author David Zülke <[email protected]> |
||
370 | * @since 0.11.0 |
||
371 | */ |
||
372 | public function hasError() |
||
376 | |||
377 | /** |
||
378 | * Whether or not this file is movable. |
||
379 | * |
||
380 | * @return bool True if this file has not been moved yet, otherwise false. |
||
381 | * |
||
382 | * @author David Zülke <[email protected]> |
||
383 | * @since 0.11.0 |
||
384 | */ |
||
385 | public function isMovable() |
||
389 | |||
390 | /** |
||
391 | * Retrieve the contents of the uploaded file. |
||
392 | * |
||
393 | * @return string The file contents. |
||
394 | * |
||
395 | * @throws Exception If the file has errors or has been moved. |
||
396 | * |
||
397 | * @author David Zülke <[email protected]> |
||
398 | * @author Peter Limbach <[email protected]> |
||
399 | * @since 0.11.2 |
||
400 | */ |
||
401 | public function getContents() |
||
417 | |||
418 | /** |
||
419 | * Check if this uploaded file object already has the file contents buffered. |
||
420 | * If this method returns false, the getContents() method will still attempt |
||
421 | * to read the file from the temporary location on disk. |
||
422 | * |
||
423 | * @return bool Whether or not the file contents are on disk yet. |
||
424 | * |
||
425 | * @author David Zülke <[email protected]> |
||
426 | * @since 1.1.0 |
||
427 | */ |
||
428 | public function hasBufferedContents() |
||
432 | |||
433 | /** |
||
434 | * Check if there is an open stream resource for this uploaded file. |
||
435 | * Will be true if this object has been constructed with a resource pointer, |
||
436 | * or if it has been constructed with raw contents and after getStream() has |
||
437 | * been called once. |
||
438 | * |
||
439 | * @return bool Whether there is an open stream with the file contents. |
||
440 | * |
||
441 | * @author David Zülke <[email protected]> |
||
442 | * @since 1.1.0 |
||
443 | */ |
||
444 | public function hasOpenStream() |
||
448 | |||
449 | /** |
||
450 | * Retrieve a stream handle of the uploaded file. |
||
451 | * |
||
452 | * @param string The fopen mode, defaults to 'rb'. Only used for files. |
||
453 | * |
||
454 | * @return resource The stream. |
||
455 | * |
||
456 | * @throws Exception If the file has errors or has been moved. |
||
457 | * |
||
458 | * @author David Zülke <[email protected]> |
||
459 | * @since 0.11.2 |
||
460 | */ |
||
461 | public function getStream($mode = 'rb') |
||
476 | |||
477 | /** |
||
478 | * Return the MIME type of this file using the fileinfo extension. |
||
479 | * |
||
480 | * @param bool Whether to return the charset of the file as well. |
||
481 | * |
||
482 | * @return string The MIME type of the file. |
||
483 | * |
||
484 | * @author David Zülke <[email protected]> |
||
485 | * @since 1.1.0 |
||
486 | */ |
||
487 | public function getMimeType($charset = false) |
||
493 | |||
494 | /** |
||
495 | * Move the uploaded file. |
||
496 | * |
||
497 | * @param string The destination filename. |
||
498 | * @param int The mode of the destination file, defaults to 0664. |
||
499 | * @param bool Whether or not subdirs should be created if necessary. |
||
500 | * @param int The mode to use when creating subdirs, defaults to 0775. |
||
501 | * |
||
502 | * @return bool True, if the operation was successful, false otherwise. |
||
503 | * |
||
504 | * @throws FileException If chmod or mkdir calls failed. |
||
505 | * |
||
506 | * @author David Zülke <[email protected]> |
||
507 | * @since 0.11.0 |
||
508 | */ |
||
509 | public function move($dest, $fileMode = 0664, $create = true, $dirMode = 0775) |
||
560 | } |
||
561 |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.