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 XmlElement 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 XmlElement, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
40 | class XmlElement |
||
41 | { |
||
42 | use Accessors; |
||
43 | |||
44 | /** |
||
45 | * Settings for tiding up XML output |
||
46 | * |
||
47 | * @var array |
||
48 | */ |
||
49 | public static $tidy = [ |
||
50 | 'indent' => true, |
||
51 | 'input-xml' => true, |
||
52 | 'output-xml' => true, |
||
53 | 'drop-empty-paras' => false, |
||
54 | 'wrap' => 0 |
||
55 | ]; |
||
56 | |||
57 | /** @var string */ |
||
58 | private $_localName; |
||
59 | /** @var null|string */ |
||
60 | private $_prefix = null; |
||
61 | |||
62 | /** @var array */ |
||
63 | private $_namespaces = []; |
||
64 | /** @var array */ |
||
65 | private $_attributes = []; |
||
66 | |||
67 | /** |
||
68 | * @var XmlElement |
||
69 | */ |
||
70 | private $_parent; |
||
71 | |||
72 | /** |
||
73 | * @var XmlElement[] |
||
74 | */ |
||
75 | private $_children = []; |
||
76 | |||
77 | /** |
||
78 | * Initializes element with given name and URI |
||
79 | * |
||
80 | * @param string $name Element name, including prefix if needed |
||
81 | * @param string $uri Namespace URI of element |
||
82 | */ |
||
83 | 19 | protected function init(string $name, string $uri = null) |
|
94 | |||
95 | /** |
||
96 | * XmlElement constructor |
||
97 | * |
||
98 | * @param string $name Element name, including prefix if needed |
||
99 | * @param string $uri Namespace URI of element |
||
100 | */ |
||
101 | 15 | public function __construct(string $name, string $uri = null) |
|
105 | |||
106 | /** |
||
107 | * Elements named constructor, same for every subclass. |
||
108 | * It's used for factory creation. |
||
109 | * |
||
110 | * @param string $name Element name, including prefix if needed |
||
111 | * @param string $uri Namespace URI of element |
||
112 | * |
||
113 | * @return XmlElement |
||
114 | */ |
||
115 | 4 | public static function plain(string $name, string $uri = null) |
|
123 | |||
124 | /** |
||
125 | * @see $innerXml |
||
126 | * @return string |
||
127 | */ |
||
128 | 2 | public function getInnerXml() |
|
140 | |||
141 | /** |
||
142 | * Returns XML representation of element |
||
143 | * |
||
144 | * @param bool $clean Result will be cleaned if set to true |
||
145 | * |
||
146 | * @return string |
||
147 | */ |
||
148 | 2 | public function xml(bool $clean = true): string |
|
169 | |||
170 | /** |
||
171 | * Looks up prefix associated with given URI |
||
172 | * |
||
173 | * @param string|null $uri |
||
174 | * @return string|false |
||
175 | */ |
||
176 | 9 | public function lookupPrefix(string $uri = null) |
|
180 | |||
181 | /** |
||
182 | * Looks up URI associated with given prefix |
||
183 | * |
||
184 | * @param string|null $prefix |
||
185 | * @return string|false |
||
186 | */ |
||
187 | 13 | public function lookupUri(string $prefix = null) |
|
191 | |||
192 | /** |
||
193 | * Returns element's namespaces |
||
194 | * |
||
195 | * @param bool $parent Include namespaces from parent? |
||
196 | * @return array |
||
197 | */ |
||
198 | 16 | public function getNamespaces($parent = true): array |
|
210 | |||
211 | /** |
||
212 | * Sets XML attribute of element |
||
213 | * |
||
214 | * For `http://www.w3.org/2000/xmlns/` URI it acts like `setNamespace($value, $attribute)` |
||
215 | * |
||
216 | * @param string $attribute Attribute name, optionally with prefix |
||
217 | * @param mixed $value Attribute value |
||
218 | * @param string|null $uri XML Namespace URI of attribute, prefix will be automatically looked up |
||
219 | */ |
||
220 | 5 | View Code Duplication | public function setAttribute(string $attribute, $value, string $uri = null) |
233 | |||
234 | /** |
||
235 | * Returns value of specified attribute. |
||
236 | * |
||
237 | * For `http://www.w3.org/2000/xmlns/` URI it acts like `lookupUri($attribute)` |
||
238 | * |
||
239 | * @param string $attribute |
||
240 | * @param string|null $uri |
||
241 | * @return bool|mixed |
||
242 | */ |
||
243 | 2 | View Code Duplication | public function getAttribute(string $attribute, string $uri = null) |
255 | |||
256 | /** |
||
257 | * Returns element's parent |
||
258 | * @return XmlElement|null |
||
259 | */ |
||
260 | 1 | public function getParent() |
|
264 | |||
265 | /** |
||
266 | * Sets element's parent |
||
267 | * @param XmlElement $parent |
||
268 | */ |
||
269 | 8 | protected function setParent(XmlElement $parent) |
|
281 | |||
282 | /** |
||
283 | * Appends child to element |
||
284 | * |
||
285 | * @param XmlElement|string $element |
||
286 | * |
||
287 | * @return XmlElement|string Same as $element |
||
288 | */ |
||
289 | 10 | public function append($element) |
|
304 | |||
305 | /** |
||
306 | * Returns namespace URI associated with element |
||
307 | * |
||
308 | * @return false|string |
||
309 | */ |
||
310 | 13 | public function getNamespace() |
|
314 | |||
315 | /** |
||
316 | * Adds namespace to element, and associates it with prefix. |
||
317 | * |
||
318 | * @param string $uri Namespace URI |
||
319 | * @param string|bool|null $prefix Prefix which will be used for namespace, false for using element's prefix |
||
320 | * and null for no prefix |
||
321 | */ |
||
322 | 13 | public function setNamespace(string $uri, $prefix = false) |
|
330 | |||
331 | 6 | public function getName() |
|
335 | |||
336 | 5 | public function getChildren() |
|
340 | |||
341 | 13 | public function getPrefix() |
|
345 | |||
346 | 8 | public function getLocalName() |
|
350 | |||
351 | 6 | public function getAttributes() |
|
355 | |||
356 | /** |
||
357 | * Returns one element at specified index (for default the first one). |
||
358 | * |
||
359 | * @param string $name Requested element tag name |
||
360 | * @param string $uri Requested element namespace |
||
361 | * @param int $index Index of element to retrieve |
||
362 | * |
||
363 | * @return XmlElement|false Retrieved element |
||
364 | */ |
||
365 | 1 | public function element(string $name, string $uri = null, int $index = 0) |
|
369 | |||
370 | /** |
||
371 | * Retrieves array of matching elements |
||
372 | * |
||
373 | * @param string $name Requested element tag name |
||
374 | * @param null $uri Requested element namespace |
||
375 | * |
||
376 | * @return XmlElement[] Found Elements |
||
377 | */ |
||
378 | 2 | public function elements($name, $uri = null) : array |
|
387 | |||
388 | /** |
||
389 | * Filters element with given predicate |
||
390 | * |
||
391 | * @param callable|string $predicate Predicate or class name |
||
392 | * |
||
393 | * @return XmlElement[] |
||
394 | */ |
||
395 | 2 | public function all($predicate) |
|
399 | |||
400 | /** |
||
401 | * @param string|null $query |
||
402 | * @return XPathQuery |
||
403 | */ |
||
404 | 1 | public function query(string $query = null) |
|
408 | |||
409 | /** |
||
410 | * Helper for retrieving all arguments (including namespaces) |
||
411 | * |
||
412 | * @return array |
||
413 | */ |
||
414 | 2 | private function attributes(): array |
|
426 | |||
427 | /** |
||
428 | * Prefixes $name with attribute associated with $uri |
||
429 | * |
||
430 | * @param string $name Name to prefix |
||
431 | * @param string $uri Namespace URI |
||
432 | * |
||
433 | * @return string |
||
434 | */ |
||
435 | 3 | protected function _prefix(string $name, string $uri): string |
|
443 | |||
444 | 1 | public function __toString() |
|
448 | |||
449 | /** |
||
450 | * Splits name into local-name and prefix |
||
451 | * |
||
452 | * @param $name |
||
453 | * @return array [$name, $prefix] |
||
454 | */ |
||
455 | 19 | public static function resolve($name) |
|
465 | } |
||
466 |
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountId
that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theid
property of an instance of theAccount
class. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.