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 CommandInfo 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 CommandInfo, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
18 | class CommandInfo |
||
19 | { |
||
20 | /** |
||
21 | * Serialization schema version. Incremented every time the serialization schema changes. |
||
22 | */ |
||
23 | const SERIALIZATION_SCHEMA_VERSION = 1; |
||
24 | |||
25 | /** |
||
26 | * @var \ReflectionMethod |
||
27 | */ |
||
28 | protected $reflection; |
||
29 | |||
30 | /** |
||
31 | * @var boolean |
||
32 | * @var string |
||
33 | */ |
||
34 | protected $docBlockIsParsed = false; |
||
35 | |||
36 | /** |
||
37 | * @var string |
||
38 | */ |
||
39 | protected $name; |
||
40 | |||
41 | /** |
||
42 | * @var string |
||
43 | */ |
||
44 | protected $description = ''; |
||
45 | |||
46 | /** |
||
47 | * @var string |
||
48 | */ |
||
49 | protected $help = ''; |
||
50 | |||
51 | /** |
||
52 | * @var DefaultsWithDescriptions |
||
53 | */ |
||
54 | protected $options; |
||
55 | |||
56 | /** |
||
57 | * @var DefaultsWithDescriptions |
||
58 | */ |
||
59 | protected $arguments; |
||
60 | |||
61 | /** |
||
62 | * @var array |
||
63 | */ |
||
64 | protected $exampleUsage = []; |
||
65 | |||
66 | /** |
||
67 | * @var AnnotationData |
||
68 | */ |
||
69 | protected $otherAnnotations; |
||
70 | |||
71 | /** |
||
72 | * @var array |
||
73 | */ |
||
74 | protected $aliases = []; |
||
75 | |||
76 | /** |
||
77 | * @var InputOption[] |
||
78 | */ |
||
79 | protected $inputOptions; |
||
80 | |||
81 | /** |
||
82 | * @var string |
||
83 | */ |
||
84 | protected $methodName; |
||
85 | |||
86 | /** |
||
87 | * @var string |
||
88 | */ |
||
89 | protected $returnType; |
||
90 | |||
91 | /** |
||
92 | * Create a new CommandInfo class for a particular method of a class. |
||
93 | * |
||
94 | * @param string|mixed $classNameOrInstance The name of a class, or an |
||
95 | * instance of it, or an array of cached data. |
||
96 | * @param string $methodName The name of the method to get info about. |
||
97 | * @param array $cache Cached data |
||
98 | * @deprecated Use CommandInfo::create() or CommandInfo::deserialize() |
||
99 | * instead. In the future, this constructor will be protected. |
||
100 | */ |
||
101 | public function __construct($classNameOrInstance, $methodName, $cache = []) |
||
115 | |||
116 | public static function create($classNameOrInstance, $methodName) |
||
120 | |||
121 | View Code Duplication | public static function deserialize($cache) |
|
130 | |||
131 | View Code Duplication | protected static function cachedMethodExists($cache) |
|
140 | |||
141 | public static function isValidSerializedData($cache) |
||
151 | |||
152 | public function cachedFileIsModified($cache) |
||
157 | |||
158 | protected function constructFromClassAndMethod($classNameOrInstance, $methodName) |
||
167 | |||
168 | protected function constructFromCache($info_array) |
||
210 | |||
211 | public function serialize() |
||
277 | |||
278 | /** |
||
279 | * Default data for serialization. |
||
280 | * @return array |
||
281 | */ |
||
282 | protected function defaultSerializationData() |
||
299 | |||
300 | /** |
||
301 | * Recover the method name provided to the constructor. |
||
302 | * |
||
303 | * @return string |
||
304 | */ |
||
305 | public function getMethodName() |
||
309 | |||
310 | /** |
||
311 | * Return the primary name for this command. |
||
312 | * |
||
313 | * @return string |
||
314 | */ |
||
315 | public function getName() |
||
320 | |||
321 | /** |
||
322 | * Set the primary name for this command. |
||
323 | * |
||
324 | * @param string $name |
||
325 | */ |
||
326 | public function setName($name) |
||
331 | |||
332 | public function getReturnType() |
||
337 | |||
338 | public function setReturnType($returnType) |
||
343 | |||
344 | /** |
||
345 | * Get any annotations included in the docblock comment for the |
||
346 | * implementation method of this command that are not already |
||
347 | * handled by the primary methods of this class. |
||
348 | * |
||
349 | * @return AnnotationData |
||
350 | */ |
||
351 | public function getRawAnnotations() |
||
356 | |||
357 | /** |
||
358 | * Get any annotations included in the docblock comment, |
||
359 | * also including default values such as @command. We add |
||
360 | * in the default @command annotation late, and only in a |
||
361 | * copy of the annotation data because we use the existance |
||
362 | * of a @command to indicate that this CommandInfo is |
||
363 | * a command, and not a hook or anything else. |
||
364 | * |
||
365 | * @return AnnotationData |
||
366 | */ |
||
367 | public function getAnnotations() |
||
380 | |||
381 | /** |
||
382 | * Return a specific named annotation for this command. |
||
383 | * |
||
384 | * @param string $annotation The name of the annotation. |
||
385 | * @return string |
||
386 | */ |
||
387 | public function getAnnotation($annotation) |
||
395 | |||
396 | /** |
||
397 | * Check to see if the specified annotation exists for this command. |
||
398 | * |
||
399 | * @param string $annotation The name of the annotation. |
||
400 | * @return boolean |
||
401 | */ |
||
402 | public function hasAnnotation($annotation) |
||
407 | |||
408 | /** |
||
409 | * Save any tag that we do not explicitly recognize in the |
||
410 | * 'otherAnnotations' map. |
||
411 | */ |
||
412 | public function addAnnotation($name, $content) |
||
416 | |||
417 | /** |
||
418 | * Remove an annotation that was previoudly set. |
||
419 | */ |
||
420 | public function removeAnnotation($name) |
||
424 | |||
425 | /** |
||
426 | * Get the synopsis of the command (~first line). |
||
427 | * |
||
428 | * @return string |
||
429 | */ |
||
430 | public function getDescription() |
||
435 | |||
436 | /** |
||
437 | * Set the command description. |
||
438 | * |
||
439 | * @param string $description The description to set. |
||
440 | */ |
||
441 | public function setDescription($description) |
||
446 | |||
447 | /** |
||
448 | * Get the help text of the command (the description) |
||
449 | */ |
||
450 | public function getHelp() |
||
455 | /** |
||
456 | * Set the help text for this command. |
||
457 | * |
||
458 | * @param string $help The help text. |
||
459 | */ |
||
460 | public function setHelp($help) |
||
465 | |||
466 | /** |
||
467 | * Return the list of aliases for this command. |
||
468 | * @return string[] |
||
469 | */ |
||
470 | public function getAliases() |
||
475 | |||
476 | /** |
||
477 | * Set aliases that can be used in place of the command's primary name. |
||
478 | * |
||
479 | * @param string|string[] $aliases |
||
480 | */ |
||
481 | public function setAliases($aliases) |
||
489 | |||
490 | /** |
||
491 | * Return the examples for this command. This is @usage instead of |
||
492 | * @example because the later is defined by the phpdoc standard to |
||
493 | * be example method calls. |
||
494 | * |
||
495 | * @return string[] |
||
496 | */ |
||
497 | public function getExampleUsages() |
||
502 | |||
503 | /** |
||
504 | * Add an example usage for this command. |
||
505 | * |
||
506 | * @param string $usage An example of the command, including the command |
||
507 | * name and all of its example arguments and options. |
||
508 | * @param string $description An explanation of what the example does. |
||
509 | */ |
||
510 | public function setExampleUsage($usage, $description) |
||
515 | |||
516 | /** |
||
517 | * Return the topics for this command. |
||
518 | * |
||
519 | * @return string[] |
||
520 | */ |
||
521 | public function getTopics() |
||
529 | |||
530 | /** |
||
531 | * Return the list of refleaction parameters. |
||
532 | * |
||
533 | * @return ReflectionParameter[] |
||
534 | */ |
||
535 | public function getParameters() |
||
539 | |||
540 | /** |
||
541 | * Descriptions of commandline arguements for this command. |
||
542 | * |
||
543 | * @return DefaultsWithDescriptions |
||
544 | */ |
||
545 | public function arguments() |
||
549 | |||
550 | /** |
||
551 | * Descriptions of commandline options for this command. |
||
552 | * |
||
553 | * @return DefaultsWithDescriptions |
||
554 | */ |
||
555 | public function options() |
||
559 | |||
560 | /** |
||
561 | * Get the inputOptions for the options associated with this CommandInfo |
||
562 | * object, e.g. via @option annotations, or from |
||
563 | * $options = ['someoption' => 'defaultvalue'] in the command method |
||
564 | * parameter list. |
||
565 | * |
||
566 | * @return InputOption[] |
||
567 | */ |
||
568 | public function inputOptions() |
||
575 | |||
576 | protected function createInputOptions() |
||
610 | |||
611 | /** |
||
612 | * An option might have a name such as 'silent|s'. In this |
||
613 | * instance, we will allow the @option or @default tag to |
||
614 | * reference the option only by name (e.g. 'silent' or 's' |
||
615 | * instead of 'silent|s'). |
||
616 | * |
||
617 | * @param string $optionName |
||
618 | * @return string |
||
619 | */ |
||
620 | public function findMatchingOption($optionName) |
||
632 | |||
633 | /** |
||
634 | * @param string $optionName |
||
635 | * @return string |
||
636 | */ |
||
637 | protected function findOptionAmongAlternatives($optionName) |
||
652 | |||
653 | /** |
||
654 | * @param string $optionName |
||
655 | * @return string|null |
||
656 | */ |
||
657 | protected function findExistingOption($optionName) |
||
668 | |||
669 | /** |
||
670 | * Examine the parameters of the method for this command, and |
||
671 | * build a list of commandline arguements for them. |
||
672 | * |
||
673 | * @return array |
||
674 | */ |
||
675 | protected function determineAgumentClassifications() |
||
688 | |||
689 | /** |
||
690 | * Examine the provided parameter, and determine whether it |
||
691 | * is a parameter that will be filled in with a positional |
||
692 | * commandline argument. |
||
693 | */ |
||
694 | protected function addParameterToResult($result, $param) |
||
711 | |||
712 | /** |
||
713 | * Examine the parameters of the method for this command, and determine |
||
714 | * the disposition of the options from them. |
||
715 | * |
||
716 | * @return array |
||
717 | */ |
||
718 | protected function determineOptionsFromParameters() |
||
733 | |||
734 | /** |
||
735 | * Helper; determine if an array is associative or not. An array |
||
736 | * is not associative if its keys are numeric, and numbered sequentially |
||
737 | * from zero. All other arrays are considered to be associative. |
||
738 | * |
||
739 | * @param array $arr The array |
||
740 | * @return boolean |
||
741 | */ |
||
742 | protected function isAssoc($arr) |
||
749 | |||
750 | /** |
||
751 | * Convert from a method name to the corresponding command name. A |
||
752 | * method 'fooBar' will become 'foo:bar', and 'fooBarBazBoz' will |
||
753 | * become 'foo:bar-baz-boz'. |
||
754 | * |
||
755 | * @param string $camel method name. |
||
756 | * @return string |
||
757 | */ |
||
758 | protected function convertName($camel) |
||
765 | |||
766 | /** |
||
767 | * Parse the docBlock comment for this command, and set the |
||
768 | * fields of this class with the data thereby obtained. |
||
769 | */ |
||
770 | protected function parseDocBlock() |
||
779 | |||
780 | /** |
||
781 | * Given a list that might be 'a b c' or 'a, b, c' or 'a,b,c', |
||
782 | * convert the data into the last of these forms. |
||
783 | */ |
||
784 | protected static function convertListToCommaSeparated($text) |
||
788 | } |
||
789 |
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.