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 |
||
43 | class XmlElement implements ContainerInterface |
||
44 | { |
||
45 | use Accessors; |
||
46 | const XMLNS = 'http://www.w3.org/2000/xmlns/'; |
||
47 | const XML = 'http://www.w3.org/XML/1998/namespace'; |
||
48 | |||
49 | /** |
||
50 | * Settings for tiding up XML output |
||
51 | * |
||
52 | * @var array |
||
53 | */ |
||
54 | public static $tidy = [ |
||
55 | 'indent' => true, |
||
56 | 'input-xml' => true, |
||
57 | 'output-xml' => true, |
||
58 | 'drop-empty-paras' => false, |
||
59 | 'wrap' => 0 |
||
60 | ]; |
||
61 | |||
62 | /** @var string */ |
||
63 | private $_localName; |
||
64 | /** @var null|string|false */ |
||
65 | private $_prefix = null; |
||
66 | |||
67 | /** @var array */ |
||
68 | private $_namespaces = []; |
||
69 | /** @var array */ |
||
70 | private $_attributes = []; |
||
71 | |||
72 | /** |
||
73 | * @var XmlElement |
||
74 | */ |
||
75 | private $_parent; |
||
76 | |||
77 | /** |
||
78 | * @var XmlElement[] |
||
79 | */ |
||
80 | private $_children = []; |
||
81 | |||
82 | /** |
||
83 | * Initializes element with given name and URI |
||
84 | * |
||
85 | * @param string $name Element name, including prefix if needed |
||
86 | * @param string $uri Namespace URI of element |
||
87 | */ |
||
88 | 39 | protected function init(string $name, string $uri = null) |
|
99 | |||
100 | /** |
||
101 | * XmlElement constructor |
||
102 | * |
||
103 | * @param string $name Element name, including prefix if needed |
||
104 | * @param string $uri Namespace URI of element |
||
105 | * @param array $options { |
||
106 | * @var mixed $content Content of element |
||
107 | * @var array $attributes Element attributes |
||
108 | * } |
||
109 | */ |
||
110 | 35 | public function __construct(string $name, string $uri = null, array $options = []) |
|
116 | |||
117 | /** |
||
118 | * Elements named constructor, same for every subclass. |
||
119 | * It's used for factory creation. |
||
120 | * |
||
121 | * @param string $name Element name, including prefix if needed |
||
122 | * @param string $uri Namespace URI of element |
||
123 | * |
||
124 | * @return static |
||
125 | */ |
||
126 | 5 | public static function plain(string $name, string $uri = null) |
|
134 | |||
135 | /** |
||
136 | * @see $innerXml |
||
137 | * @return string |
||
138 | */ |
||
139 | 6 | public function getInnerXml() |
|
151 | |||
152 | public function setInnerXml($value) |
||
158 | |||
159 | public function getContent() |
||
163 | |||
164 | 5 | public function setContent($value) |
|
169 | |||
170 | /** |
||
171 | * Returns XML representation of element |
||
172 | * |
||
173 | * @param bool $clean Result will be cleaned if set to true |
||
174 | * |
||
175 | * @return string |
||
176 | */ |
||
177 | 4 | public function xml(bool $clean = true): string |
|
198 | |||
199 | /** |
||
200 | * Looks up prefix associated with given URI |
||
201 | * |
||
202 | * @param string|null $uri |
||
203 | * @return string|false |
||
204 | */ |
||
205 | 22 | public function lookupPrefix(string $uri = null) |
|
209 | |||
210 | /** |
||
211 | * Looks up URI associated with given prefix |
||
212 | * |
||
213 | * @param string|null $prefix |
||
214 | * @return string|false |
||
215 | */ |
||
216 | 25 | public function lookupUri(string $prefix = null) |
|
220 | |||
221 | /** |
||
222 | * Returns element's namespaces |
||
223 | * |
||
224 | * @param bool $parent Include namespaces from parent? |
||
225 | * @return array |
||
226 | */ |
||
227 | 30 | public function getNamespaces($parent = true): array |
|
239 | |||
240 | /** |
||
241 | * Sets XML attribute of element |
||
242 | * |
||
243 | * @param string $attribute Attribute name, optionally with prefix |
||
244 | * @param mixed $value Attribute value |
||
245 | * @param string|null $uri XML Namespace URI of attribute, prefix will be automatically looked up |
||
246 | */ |
||
247 | 20 | public function setAttribute(string $attribute, $value, string $uri = null) |
|
258 | |||
259 | /** |
||
260 | * Returns value of specified attribute. |
||
261 | * |
||
262 | * @param string $attribute Attribute name, optionally with prefix |
||
263 | * @param string|null $uri XML Namespace URI of attribute, prefix will be automatically looked up |
||
264 | * @return bool|mixed |
||
265 | */ |
||
266 | 13 | public function getAttribute(string $attribute, string $uri = null) |
|
270 | |||
271 | /** |
||
272 | * Checks if attribute exists |
||
273 | * |
||
274 | * @param string $attribute Attribute name, optionally with prefix |
||
275 | * @param string|null $uri XML Namespace URI of attribute, prefix will be automatically looked up |
||
276 | * |
||
277 | * @return bool |
||
278 | */ |
||
279 | 5 | public function hasAttribute(string $attribute, string $uri = null) |
|
283 | |||
284 | /** |
||
285 | * Returns all element's parents in order, oldest ancestor is the last element in returned array. |
||
286 | * @return XmlElement[] |
||
287 | */ |
||
288 | public function getParents() |
||
292 | |||
293 | /** |
||
294 | * Returns element's parent |
||
295 | * @return XmlElement|null |
||
296 | */ |
||
297 | 3 | public function getParent() |
|
301 | |||
302 | /** |
||
303 | * Sets element's parent |
||
304 | * @param XmlElement $parent |
||
305 | */ |
||
306 | 18 | protected function setParent(XmlElement $parent) |
|
318 | |||
319 | /** |
||
320 | * Appends child to element |
||
321 | * |
||
322 | * @param XmlElement|string $element |
||
323 | * |
||
324 | * @return XmlElement|string Same as $element |
||
325 | */ |
||
326 | 23 | public function append($element) |
|
339 | |||
340 | 4 | public function remove($element) |
|
354 | |||
355 | 22 | protected function appendChild($element) { |
|
371 | |||
372 | /** |
||
373 | * Returns namespace URI associated with element or specified prefix |
||
374 | * |
||
375 | * @param string|bool|null $prefix |
||
376 | * @return false|string |
||
377 | */ |
||
378 | 25 | public function getNamespace($prefix = false) |
|
386 | |||
387 | /** |
||
388 | * Adds namespace to element, and associates it with prefix. |
||
389 | * |
||
390 | * @param string $uri Namespace URI |
||
391 | * @param string|bool|null $prefix Prefix which will be used for namespace, false for using element's prefix |
||
392 | * and null for no prefix |
||
393 | */ |
||
394 | 28 | public function setNamespace(string $uri, $prefix = false) |
|
402 | |||
403 | 9 | public function getFullName() |
|
407 | |||
408 | 12 | public function getChildren() |
|
412 | |||
413 | 25 | public function getPrefix() |
|
417 | |||
418 | 20 | public function getLocalName() |
|
422 | |||
423 | 7 | public function getAttributes() |
|
427 | |||
428 | 2 | protected function setAttributes(array $attributes) |
|
436 | |||
437 | /** |
||
438 | * Returns one element at specified index (for default the first one). |
||
439 | * |
||
440 | * @param string $name Requested element tag name |
||
441 | * @param string $uri Requested element namespace |
||
442 | * @param int $index Index of element to retrieve |
||
443 | * |
||
444 | * @return XmlElement|false Retrieved element |
||
445 | */ |
||
446 | 1 | public function element(string $name, string $uri = null, int $index = 0) |
|
450 | |||
451 | /** |
||
452 | * Retrieves array of matching elements |
||
453 | * |
||
454 | * @param string $name Requested element tag name |
||
455 | * @param string|null $uri Requested element namespace |
||
456 | * |
||
457 | * @return XmlElement[] Found Elements |
||
458 | */ |
||
459 | 2 | View Code Duplication | public function elements($name, $uri = null) : array |
468 | |||
469 | /** |
||
470 | * Filters element with given predicate |
||
471 | * |
||
472 | * @param callable|string $predicate Predicate or class name |
||
473 | * |
||
474 | * @return XmlElement[] |
||
475 | */ |
||
476 | 2 | public function all($predicate) |
|
480 | |||
481 | /** |
||
482 | * Iterates over matching elements |
||
483 | * |
||
484 | * @param callable|string $predicate Predicate or class name |
||
485 | * |
||
486 | * @return XmlElement|false |
||
487 | */ |
||
488 | 8 | public function get($predicate) |
|
499 | |||
500 | /** |
||
501 | * Checks if any element matching predicate exists |
||
502 | * |
||
503 | * @param callable|string $predicate Predicate or class name |
||
504 | * |
||
505 | * @return bool |
||
506 | */ |
||
507 | 1 | public function has($predicate) |
|
511 | |||
512 | /** |
||
513 | * @param string|null $query |
||
514 | * @return XPathQuery |
||
515 | */ |
||
516 | 1 | public function query(string $query = null) |
|
520 | |||
521 | /** |
||
522 | * Helper for retrieving all arguments (including namespaces) |
||
523 | * |
||
524 | * @return array |
||
525 | */ |
||
526 | 4 | private function attributes(): array |
|
538 | |||
539 | /** |
||
540 | * Prefixes $name with attribute associated with $uri |
||
541 | * |
||
542 | * @param string $name Name to prefix |
||
543 | * @param string $uri Namespace URI |
||
544 | * |
||
545 | * @return string |
||
546 | */ |
||
547 | 20 | protected function _prefix(string $name, string $uri = null): string |
|
559 | |||
560 | 3 | public function __toString() |
|
564 | |||
565 | /** |
||
566 | * Splits name into local-name and prefix |
||
567 | * |
||
568 | * @param $name |
||
569 | * @return array [$name, $prefix] |
||
570 | */ |
||
571 | 39 | public static function resolve($name) |
|
581 | |||
582 | /** |
||
583 | * Casts XML Element object to another class, it's not recommended but should work, as child classes should |
||
584 | * only decorate parent with additional getters and setters for accessing data. |
||
585 | * |
||
586 | * @param XmlElement $element |
||
587 | * @return static |
||
588 | */ |
||
589 | 1 | public static function cast(XmlElement $element) |
|
599 | |||
600 | /** |
||
601 | * When an object is cloned, PHP 5 will perform a shallow copy of all of the object's properties. |
||
602 | * Any properties that are references to other variables, will remain references. |
||
603 | * Once the cloning is complete, if a __clone() method is defined, |
||
604 | * then the newly created object's __clone() method will be called, to allow any necessary properties that need to be changed. |
||
605 | * NOT CALLABLE DIRECTLY. |
||
606 | * |
||
607 | * @link http://php.net/manual/en/language.oop5.cloning.php |
||
608 | */ |
||
609 | public function __clone() |
||
619 | } |
||
620 |
This check looks at variables that have been passed in as parameters and are passed out again to other methods.
If the outgoing method call has stricter type requirements than the method itself, an issue is raised.
An additional type check may prevent trouble.