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 Clazz 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 Clazz, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
26 | class Clazz |
||
27 | { |
||
28 | /** |
||
29 | * @var string |
||
30 | */ |
||
31 | private $namespaceName; |
||
32 | |||
33 | /** |
||
34 | * @var string |
||
35 | */ |
||
36 | private $shortClassName; |
||
37 | |||
38 | /** |
||
39 | * @var string |
||
40 | */ |
||
41 | private $directory; |
||
42 | |||
43 | /** |
||
44 | * @var string |
||
45 | */ |
||
46 | private $fileName; |
||
47 | |||
48 | /** |
||
49 | * @var string |
||
50 | */ |
||
51 | private $parentClass; |
||
52 | |||
53 | /** |
||
54 | * @var bool[] |
||
55 | */ |
||
56 | private $implementedInterfaces = array(); |
||
57 | |||
58 | /** |
||
59 | * @var Import[] |
||
60 | */ |
||
61 | private $imports = array(); |
||
62 | |||
63 | /** |
||
64 | * @var string[] |
||
65 | */ |
||
66 | private $importedSymbols = array(); |
||
67 | |||
68 | /** |
||
69 | * @var Method[] |
||
70 | */ |
||
71 | private $methods = array(); |
||
72 | |||
73 | /** |
||
74 | * @var string |
||
75 | */ |
||
76 | private $description; |
||
77 | |||
78 | /** |
||
79 | * Creates a new factory class. |
||
80 | * |
||
81 | * @param string $className The fully-qualified class name. |
||
82 | */ |
||
83 | 208 | public function __construct($className) |
|
87 | |||
88 | /** |
||
89 | * Sets the fully-qualified name of the factory class. |
||
90 | * |
||
91 | * @param string $className The fully-qualified class name. |
||
92 | * |
||
93 | * @return static The current instance. |
||
94 | */ |
||
95 | 208 | public function setClassName($className) |
|
111 | |||
112 | /** |
||
113 | * Returns the fully-qualified name of the factory class. |
||
114 | * |
||
115 | * @return string The fully-qualified class name. |
||
116 | */ |
||
117 | 9 | public function getClassName() |
|
123 | |||
124 | /** |
||
125 | * Returns the namespace of the factory class. |
||
126 | * |
||
127 | * @return string The namespace or an empty string if the class is in the |
||
128 | * global namespace. |
||
129 | */ |
||
130 | 53 | public function getNamespaceName() |
|
134 | |||
135 | /** |
||
136 | * Returns the short class name. |
||
137 | * |
||
138 | * @return string The short name of the factory class. |
||
139 | */ |
||
140 | 53 | public function getShortClassName() |
|
144 | |||
145 | /** |
||
146 | * Returns the path to the directory holding the factory class file. |
||
147 | * |
||
148 | * @return string The absolute directory path. |
||
149 | */ |
||
150 | 91 | public function getDirectory() |
|
154 | |||
155 | /** |
||
156 | * Sets the path to the directory holding the factory class file. |
||
157 | * |
||
158 | * @param string $directory The absolute directory path. |
||
159 | * |
||
160 | * @return static The current instance. |
||
161 | */ |
||
162 | 118 | public function setDirectory($directory) |
|
170 | |||
171 | /** |
||
172 | * Returns the name of the factory class file. |
||
173 | * |
||
174 | * If no file name was set, the file is named after the short class name |
||
175 | * suffixed with ".php". |
||
176 | * |
||
177 | * @return string The file name. |
||
178 | */ |
||
179 | 60 | public function getFileName() |
|
187 | |||
188 | /** |
||
189 | * Sets the name of the factory class file. |
||
190 | * |
||
191 | * @param string $fileName The file name. |
||
192 | * |
||
193 | * @return static The current instance. |
||
194 | */ |
||
195 | 90 | public function setFileName($fileName) |
|
203 | |||
204 | /** |
||
205 | * Resets the name of the factory class file to the default value. |
||
206 | * |
||
207 | * By default, the file is named after the short class name suffixed with |
||
208 | * ".php". |
||
209 | * |
||
210 | * @return static The current instance. |
||
211 | */ |
||
212 | 1 | public function resetFileName() |
|
218 | |||
219 | /** |
||
220 | * Returns the absolute file path of the factory class file. |
||
221 | * |
||
222 | * @return string The absolute file path. |
||
223 | */ |
||
224 | 51 | public function getFilePath() |
|
228 | |||
229 | /** |
||
230 | * Sets the absolute file path of the factory class file. |
||
231 | * |
||
232 | * @param string $filePath The absolute file path. |
||
233 | * |
||
234 | * @return static The current instance. |
||
235 | */ |
||
236 | 86 | public function setFilePath($filePath) |
|
245 | |||
246 | /** |
||
247 | * Returns the parent class name. |
||
248 | * |
||
249 | * @return string|null The parent class name or `null` if the class has no |
||
250 | * parent class. |
||
251 | */ |
||
252 | 4 | public function getParentClass() |
|
256 | |||
257 | /** |
||
258 | * Sets the parent class name. |
||
259 | * |
||
260 | * If you don't pass a fully-qualified name, make sure to import the name |
||
261 | * with {@link addImport()}. |
||
262 | * |
||
263 | * @param string $parentClass The parent class name. |
||
264 | * |
||
265 | * @return static The current instance. |
||
266 | */ |
||
267 | 8 | public function setParentClass($parentClass) |
|
275 | |||
276 | /** |
||
277 | * Returns whether the class extends another class. |
||
278 | * |
||
279 | * @return bool Returns `true` if the class has a parent class and `false` |
||
280 | * otherwise. |
||
281 | */ |
||
282 | 51 | public function hasParentClass() |
|
286 | |||
287 | /** |
||
288 | * Removes the parent class. |
||
289 | * |
||
290 | * @return static The current instance. |
||
291 | */ |
||
292 | 2 | public function removeParentClass() |
|
298 | |||
299 | /** |
||
300 | * Returns the interfaces that the class implements. |
||
301 | * |
||
302 | * @return string[] The names of the implemented interfaces. |
||
303 | */ |
||
304 | 8 | public function getImplementedInterfaces() |
|
308 | |||
309 | /** |
||
310 | * Returns whether the class implements any interfaces. |
||
311 | * |
||
312 | * @return bool Returns `true` if the class implements any interfaces and |
||
313 | * `false` otherwise. |
||
314 | */ |
||
315 | 51 | public function hasImplementedInterfaces() |
|
319 | |||
320 | /** |
||
321 | * Sets the interfaces that the class implements. |
||
322 | * |
||
323 | * Previously added interfaces are overwritten. |
||
324 | * |
||
325 | * If you don't pass fully-qualified names, make sure to import the names |
||
326 | * with {@link addImport()}. |
||
327 | * |
||
328 | * @param string[] $interfaceNames The names of the implemented interfaces. |
||
329 | * |
||
330 | * @return static The current instance. |
||
331 | */ |
||
332 | 1 | public function setImplementedInterfaces(array $interfaceNames) |
|
340 | |||
341 | /** |
||
342 | * Adds implemented interfaces to the class definition. |
||
343 | * |
||
344 | * Previously added interfaces are kept. |
||
345 | * |
||
346 | * If you don't pass fully-qualified names, make sure to import the names |
||
347 | * with {@link addImport()}. |
||
348 | * |
||
349 | * @param string[] $interfaceNames The names of the added interfaces. |
||
350 | * |
||
351 | * @return static The current instance. |
||
352 | */ |
||
353 | 2 | public function addImplementedInterfaces(array $interfaceNames) |
|
361 | |||
362 | /** |
||
363 | * Adds an implemented interface to the class definition. |
||
364 | * |
||
365 | * If you don't pass a fully-qualified name, make sure to import the name |
||
366 | * with {@link addImport()}. |
||
367 | * |
||
368 | * @param string $interfaceName The name of the added interfaces. |
||
369 | * |
||
370 | * @return static The current instance. |
||
371 | */ |
||
372 | 12 | public function addImplementedInterface($interfaceName) |
|
380 | |||
381 | /** |
||
382 | * Removes an implemented interface from the class definition. |
||
383 | * |
||
384 | * @param string $interfaceName The name of the removed interfaces. |
||
385 | * |
||
386 | * @return static The current instance. |
||
387 | */ |
||
388 | 3 | public function removeImplementedInterface($interfaceName) |
|
394 | |||
395 | /** |
||
396 | * Removes all implemented interfaces from the class definition. |
||
397 | * |
||
398 | * @return static The current instance. |
||
399 | */ |
||
400 | 1 | public function clearImplementedInterfaces() |
|
406 | |||
407 | /** |
||
408 | * Returns the import statements of the class file. |
||
409 | * |
||
410 | * @return Import[] The imported fully-qualified class names. |
||
411 | */ |
||
412 | 40 | public function getImports() |
|
416 | |||
417 | /** |
||
418 | * Returns whether the class file imports any class name. |
||
419 | * |
||
420 | * @return bool Returns `true` if the class file imports class names and |
||
421 | * `false` otherwise. |
||
422 | */ |
||
423 | 51 | public function hasImports() |
|
427 | |||
428 | /** |
||
429 | * Sets the import statements of the class file. |
||
430 | * |
||
431 | * Previously added imports are overwritten. |
||
432 | * |
||
433 | * @param Import[] $imports The imported fully-qualified class names. |
||
434 | * |
||
435 | * @return static The current instance. |
||
436 | */ |
||
437 | 1 | public function setImports(array $imports) |
|
445 | |||
446 | /** |
||
447 | * Adds import statements to the class file. |
||
448 | * |
||
449 | * Previously added imports are kept. |
||
450 | * |
||
451 | * @param Import[] $imports The imported fully-qualified class names. |
||
452 | * |
||
453 | * @return static The current instance. |
||
454 | */ |
||
455 | 2 | public function addImports(array $imports) |
|
463 | |||
464 | /** |
||
465 | * Adds an import statement to the class file. |
||
466 | * |
||
467 | * @param Import $import The imported fully-qualified class name. |
||
468 | * |
||
469 | * @return static The current instance. |
||
470 | */ |
||
471 | 72 | public function addImport(Import $import) |
|
493 | |||
494 | /** |
||
495 | * Removes an import statement from the class file. |
||
496 | * |
||
497 | * If the import statement is not found, this method does nothing. |
||
498 | * |
||
499 | * @param string $className The removed imported class name. |
||
500 | * |
||
501 | * @return static The current instance. |
||
502 | */ |
||
503 | 2 | public function removeImport($className) |
|
515 | |||
516 | /** |
||
517 | * Removes all import statements from the class file. |
||
518 | * |
||
519 | * @return static The current instance. |
||
520 | */ |
||
521 | 1 | public function clearImports() |
|
527 | |||
528 | /** |
||
529 | * Returns the methods of the factory class. |
||
530 | * |
||
531 | * @return Method[] The methods indexed by their names. |
||
532 | */ |
||
533 | 56 | public function getMethods() |
|
537 | |||
538 | /** |
||
539 | * Returns the method with the given name. |
||
540 | * |
||
541 | * @param string $name The name of the method. |
||
542 | * |
||
543 | * @return Method The method. |
||
544 | * |
||
545 | * @throws OutOfBoundsException If the method with the given name does not |
||
546 | * exist. |
||
547 | */ |
||
548 | 2 | public function getMethod($name) |
|
559 | |||
560 | /** |
||
561 | * Returns whether the class contains any methods. |
||
562 | * |
||
563 | * @return bool Returns `true` if the class contains methods and `false` |
||
564 | * otherwise. |
||
565 | */ |
||
566 | 1 | public function hasMethods() |
|
570 | |||
571 | /** |
||
572 | * Returns whether the class contains a method with the given name. |
||
573 | * |
||
574 | * @param string $name The name of the method. |
||
575 | * |
||
576 | * @return bool Returns `true` if the method with the given name exists and |
||
577 | * `false` otherwise. |
||
578 | */ |
||
579 | 2 | public function hasMethod($name) |
|
583 | |||
584 | /** |
||
585 | * Sets the methods of the class. |
||
586 | * |
||
587 | * Previously added methods are overwritten. |
||
588 | * |
||
589 | * @param Method[] $methods The methods of the class. |
||
590 | * |
||
591 | * @return static The current instance. |
||
592 | */ |
||
593 | 1 | public function setMethods(array $methods) |
|
601 | |||
602 | /** |
||
603 | * Adds methods to the class. |
||
604 | * |
||
605 | * Previously added methods are kept. |
||
606 | * |
||
607 | * @param Method[] $methods The methods to add to the class. |
||
608 | * |
||
609 | * @return static The current instance. |
||
610 | */ |
||
611 | 2 | public function addMethods(array $methods) |
|
619 | |||
620 | /** |
||
621 | * Adds a method to the class. |
||
622 | * |
||
623 | * @param Method $method The method to add to the class. |
||
624 | * |
||
625 | * @return static The current instance. |
||
626 | */ |
||
627 | 110 | public function addMethod(Method $method) |
|
642 | |||
643 | /** |
||
644 | * Removes a method from the class. |
||
645 | * |
||
646 | * @param string $name The name of the removed method. |
||
647 | * |
||
648 | * @return static The current instance. |
||
649 | */ |
||
650 | 4 | public function removeMethod($name) |
|
656 | |||
657 | /** |
||
658 | * Removes all methods from the class. |
||
659 | * |
||
660 | * @return static The current instance. |
||
661 | */ |
||
662 | 1 | public function clearMethods() |
|
668 | |||
669 | /** |
||
670 | * Returns the description of the class. |
||
671 | * |
||
672 | * @return string The class description. |
||
673 | */ |
||
674 | 51 | public function getDescription() |
|
678 | |||
679 | /** |
||
680 | * Sets the description of the class. |
||
681 | * |
||
682 | * @param string $description The class description. |
||
683 | * |
||
684 | * @return static The current instance. |
||
685 | */ |
||
686 | 33 | public function setDescription($description) |
|
694 | } |
||
695 |
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.