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 Element 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 Element, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
8 | class Element extends AbstractToken implements Cleanable, ContainsChildren, Removable |
||
9 | { |
||
10 | /** @var array */ |
||
11 | protected $attributes; |
||
12 | |||
13 | /** @var Token[] */ |
||
14 | protected $children; |
||
15 | |||
16 | /** @var string */ |
||
17 | private $name; |
||
18 | |||
19 | /** |
||
20 | * Constructor |
||
21 | */ |
||
22 | 185 | public function __construct(Configuration $configuration, |
|
23 | int $line, |
||
24 | int $position, |
||
25 | string $name, |
||
26 | array $attributes = array()) |
||
27 | { |
||
28 | 185 | parent::__construct($configuration, $line, $position); |
|
29 | |||
30 | 185 | $this->attributes = array(); |
|
31 | 185 | foreach ($attributes as $key => $value) { |
|
32 | 99 | $this->addAttribute($key, $value); |
|
33 | } |
||
34 | |||
35 | 185 | $this->children = array(); |
|
36 | |||
37 | 185 | if (!is_string($name)) { |
|
38 | throw new \InvalidArgumentException('Element name must be string type.'); |
||
39 | } |
||
40 | |||
41 | 185 | $this->name = trim(strtolower($name)); |
|
42 | 185 | } |
|
43 | |||
44 | /** |
||
45 | * Getter for 'attributes'. |
||
46 | */ |
||
47 | 2 | public function getAttributes() : array |
|
48 | { |
||
49 | 2 | $attributeArray = array(); |
|
50 | 2 | foreach ($this->attributes as $attribute) { |
|
51 | 1 | $attributeArray[$attribute->getName()] = $attribute->getValue(); |
|
52 | } |
||
53 | |||
54 | 2 | return $attributeArray; |
|
55 | } |
||
56 | |||
57 | 4 | public function getAttribute(string $key) |
|
58 | { |
||
59 | 4 | if (!$this->hasAttribute($key)) { |
|
60 | 1 | throw new \InvalidArgumentException('Invalid attribute key: ' . $key); |
|
61 | } |
||
62 | |||
63 | 3 | $attributeObject = $this->attributes[$key]; |
|
64 | |||
65 | 3 | return $attributeObject->getValue(); |
|
66 | } |
||
67 | |||
68 | /** |
||
69 | * Hasser for 'attributes'. |
||
70 | * |
||
71 | * @param string $key |
||
72 | * |
||
73 | * @return bool True if the attribute is present. |
||
74 | */ |
||
75 | 45 | public function hasAttribute(string $key) : bool |
|
76 | { |
||
77 | 45 | return array_key_exists($key, $this->attributes); |
|
78 | } |
||
79 | |||
80 | 102 | public function addAttribute(string $key, $value) |
|
81 | { |
||
82 | 102 | $key = trim(strtolower($key)); |
|
83 | 102 | if ($key === '') { |
|
84 | 1 | throw new \InvalidArgumentException('Invalid empty attribute key.'); |
|
85 | } |
||
86 | |||
87 | 101 | $attributeParameters = $this->getAttributeParameters($key); |
|
88 | 101 | $isStandard = true; |
|
89 | 101 | View Code Duplication | if (empty($attributeParameters)) { |
|
|||
90 | $attributeParameters = array( |
||
91 | 11 | 'name' => $key, |
|
92 | 11 | 'regex' => '/\S*/i', |
|
93 | 'valueType' => Attribute::CS_STRING |
||
94 | ); |
||
95 | 11 | $isStandard = false; |
|
96 | } |
||
97 | |||
98 | 101 | $this->attributes[$key] = new Attribute( |
|
99 | 101 | $key, |
|
100 | 101 | $value, |
|
101 | 101 | $attributeParameters['valueType'], |
|
102 | 101 | $isStandard |
|
103 | ); |
||
104 | |||
105 | 101 | return $this; |
|
106 | } |
||
107 | |||
108 | 2 | public function removeAttribute(string $key) |
|
109 | { |
||
110 | 2 | $key = trim(strtolower($key)); |
|
111 | 2 | if (isset($this->attributes[$key])) { |
|
112 | 2 | unset($this->attributes[$key]); |
|
113 | } |
||
114 | 2 | } |
|
115 | |||
116 | /** |
||
117 | * Required by ContainsChildren interface. |
||
118 | */ |
||
119 | 3 | public function getChildren() : array |
|
123 | |||
124 | /** |
||
125 | * Required by ContainsChildren interface. |
||
126 | */ |
||
127 | 1 | public function hasChild(Token $token) : bool |
|
131 | |||
132 | /** |
||
133 | * Required by ContainsChildren interface. |
||
134 | */ |
||
135 | 140 | public function appendChild(Token $token) |
|
136 | { |
||
137 | 140 | $token->setParent($this); |
|
140 | |||
141 | /** |
||
142 | * Required by ContainsChildren interface. |
||
143 | */ |
||
144 | 5 | public function prependChild(Token $token) |
|
149 | |||
150 | /** |
||
151 | * Required by the ContainsChildren interface. |
||
152 | */ |
||
153 | 39 | View Code Duplication | public function removeChild(Token $token) |
160 | |||
161 | /** |
||
162 | * Getter for 'name'. |
||
163 | */ |
||
164 | 137 | public function getName() : string |
|
168 | |||
169 | /** |
||
170 | * Required by the Cleanable interface. |
||
171 | */ |
||
172 | 140 | public function clean(LoggerInterface $logger) : bool |
|
230 | |||
231 | 132 | protected function fixSelf(LoggerInterface $logger) |
|
234 | |||
235 | 118 | protected function removeInvalidChildren(LoggerInterface $logger) |
|
238 | |||
239 | 129 | protected function removeInvalidSelf(LoggerInterface $logger) : bool |
|
243 | |||
244 | 101 | protected function getAllowedAttributes() |
|
395 | |||
396 | 101 | private function getAttributeParameters(string $key) : array |
|
411 | |||
412 | /** |
||
413 | * Required by the Removable interface. |
||
414 | */ |
||
415 | 137 | public function remove(LoggerInterface $logger) |
|
445 | |||
446 | /** |
||
447 | * Required by the Token interface. |
||
448 | */ |
||
449 | 9 | public function toHtml(string $prefix, string $suffix) : string |
|
460 | |||
461 | 143 | protected function buildStartTag(string $prefix, string $suffix, bool $forceOpen = false) : string |
|
474 | |||
475 | 139 | protected function buildChildrenHtml(string $prefix, string $suffix) : string |
|
489 | |||
490 | 27 | public function getType() : string |
|
494 | |||
495 | 91 | public function __toString() |
|
500 | } |
||
501 |
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.