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 Filesystem 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 Filesystem, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
13 | class Filesystem |
||
14 | { |
||
15 | protected $adapter; |
||
16 | |||
17 | /** |
||
18 | * Contains File objects created with $this->createFile() method. |
||
19 | * |
||
20 | * @var array |
||
21 | */ |
||
22 | protected $fileRegister = array(); |
||
23 | |||
24 | /** |
||
25 | * @param Adapter $adapter A configured Adapter instance |
||
26 | */ |
||
27 | public function __construct(Adapter $adapter) |
||
28 | { |
||
29 | $this->adapter = $adapter; |
||
30 | } |
||
31 | |||
32 | /** |
||
33 | * Returns the adapter. |
||
34 | * |
||
35 | * @return Adapter |
||
36 | */ |
||
37 | public function getAdapter() |
||
38 | { |
||
39 | return $this->adapter; |
||
40 | } |
||
41 | |||
42 | /** |
||
43 | * Indicates whether the file matching the specified key exists. |
||
44 | * |
||
45 | * @param string $key |
||
46 | * |
||
47 | * @return bool TRUE if the file exists, FALSE otherwise |
||
48 | */ |
||
49 | public function has($key) |
||
50 | { |
||
51 | return $this->adapter->exists($key); |
||
52 | } |
||
53 | |||
54 | /** |
||
55 | * Renames a file. |
||
56 | * |
||
57 | * @param string $sourceKey |
||
58 | * @param string $targetKey |
||
59 | * |
||
60 | * @return bool TRUE if the rename was successful |
||
61 | * |
||
62 | * @throws Exception\FileNotFound when sourceKey does not exist |
||
63 | * @throws Exception\UnexpectedFile when targetKey exists |
||
64 | * @throws \RuntimeException when cannot rename |
||
65 | */ |
||
66 | public function rename($sourceKey, $targetKey) |
||
85 | |||
86 | /** |
||
87 | * Returns the file matching the specified key. |
||
88 | * |
||
89 | * @param string $key Key of the file |
||
90 | * @param bool $create Whether to create the file if it does not exist |
||
91 | * |
||
92 | * @throws Exception\FileNotFound |
||
93 | * |
||
94 | * @return File |
||
95 | */ |
||
96 | public function get($key, $create = false) |
||
97 | { |
||
98 | if (!$create) { |
||
99 | $this->assertHasFile($key); |
||
100 | } |
||
101 | |||
102 | return $this->createFile($key); |
||
103 | } |
||
104 | |||
105 | /** |
||
106 | * Writes the given content into the file. |
||
107 | * |
||
108 | * @param string $key Key of the file |
||
109 | * @param string $content Content to write in the file |
||
110 | * @param bool $overwrite Whether to overwrite the file if exists |
||
111 | * |
||
112 | * @throws Exception\FileAlreadyExists When file already exists and overwrite is false |
||
113 | * @throws \RuntimeException When for any reason content could not be written |
||
114 | * |
||
115 | * @return int The number of bytes that were written into the file |
||
116 | */ |
||
117 | public function write($key, $content, $overwrite = false) |
||
131 | |||
132 | /** |
||
133 | * Reads the content from the file. |
||
134 | * |
||
135 | * @param string $key Key of the file |
||
136 | * |
||
137 | * @throws Exception\FileNotFound when file does not exist |
||
138 | * @throws \RuntimeException when cannot read file |
||
139 | * |
||
140 | * @return string |
||
141 | */ |
||
142 | View Code Duplication | public function read($key) |
|
154 | |||
155 | /** |
||
156 | * Deletes the file matching the specified key. |
||
157 | * |
||
158 | * @param string $key |
||
159 | * |
||
160 | * @throws \RuntimeException when cannot read file |
||
161 | * |
||
162 | * @return bool |
||
163 | */ |
||
164 | View Code Duplication | public function delete($key) |
|
176 | |||
177 | /** |
||
178 | * Returns an array of all keys. |
||
179 | * |
||
180 | * @return array |
||
181 | */ |
||
182 | public function keys() |
||
186 | |||
187 | /** |
||
188 | * Lists keys beginning with given prefix |
||
189 | * (no wildcard / regex matching). |
||
190 | * |
||
191 | * if adapter implements ListKeysAware interface, adapter's implementation will be used, |
||
192 | * in not, ALL keys will be requested and iterated through. |
||
193 | * |
||
194 | * @param string $prefix |
||
195 | * |
||
196 | * @return array |
||
197 | */ |
||
198 | public function listKeys($prefix = '') |
||
222 | |||
223 | /** |
||
224 | * Lists files beginning with given prefix |
||
225 | * (no wildcard / regex matching) |
||
226 | * |
||
227 | * if adapter implements ListFilesAware interface, adapter's implementation will be used, |
||
228 | * otherwise return empty array |
||
229 | * |
||
230 | * @param string $prefix |
||
231 | * @return array |
||
232 | */ |
||
233 | public function listFiles($prefix = '') |
||
241 | |||
242 | /** |
||
243 | * Returns the last modified time of the specified file. |
||
244 | * |
||
245 | * @param string $key |
||
246 | * |
||
247 | * @return int An UNIX like timestamp |
||
248 | */ |
||
249 | public function mtime($key) |
||
255 | |||
256 | /** |
||
257 | * Returns the checksum of the specified file's content. |
||
258 | * |
||
259 | * @param string $key |
||
260 | * |
||
261 | * @return string A MD5 hash |
||
262 | */ |
||
263 | public function checksum($key) |
||
273 | |||
274 | /** |
||
275 | * Returns the size of the specified file's content. |
||
276 | * |
||
277 | * @param string $key |
||
278 | * |
||
279 | * @return int File size in Bytes |
||
280 | */ |
||
281 | public function size($key) |
||
291 | |||
292 | /** |
||
293 | * Gets a new stream instance of the specified file. |
||
294 | * |
||
295 | * @param $key |
||
296 | * |
||
297 | * @return Stream|Stream\InMemoryBuffer |
||
298 | */ |
||
299 | public function createStream($key) |
||
307 | |||
308 | /** |
||
309 | * Creates a new file in a filesystem. |
||
310 | * |
||
311 | * @param $key |
||
312 | * |
||
313 | * @return File |
||
314 | */ |
||
315 | public function createFile($key) |
||
327 | |||
328 | /** |
||
329 | * Get the mime type of the provided key. |
||
330 | * |
||
331 | * @param string $key |
||
332 | * |
||
333 | * @return string |
||
334 | */ |
||
335 | public function mimeType($key) |
||
348 | |||
349 | /** |
||
350 | * Checks if matching file by given key exists in the filesystem. |
||
351 | * |
||
352 | * Key must be non empty string, otherwise it will throw Exception\FileNotFound |
||
353 | * {@see http://php.net/manual/en/function.empty.php} |
||
354 | * |
||
355 | * @param string $key |
||
356 | * |
||
357 | * @throws Exception\FileNotFound when sourceKey does not exist |
||
358 | */ |
||
359 | private function assertHasFile($key) |
||
365 | |||
366 | /** |
||
367 | * Checks if matching File object by given key exists in the fileRegister. |
||
368 | * |
||
369 | * @param string $key |
||
370 | * |
||
371 | * @return bool |
||
372 | */ |
||
373 | private function isFileInRegister($key) |
||
377 | |||
378 | /** |
||
379 | * Clear files register. |
||
380 | */ |
||
381 | public function clearFileRegister() |
||
385 | |||
386 | /** |
||
387 | * Removes File object from register. |
||
388 | * |
||
389 | * @param string $key |
||
390 | */ |
||
391 | public function removeFromRegister($key) |
||
397 | |||
398 | /** |
||
399 | * @param string $key |
||
400 | * |
||
401 | * @return bool |
||
402 | */ |
||
403 | public function isDirectory($key) |
||
407 | } |
||
408 |
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.