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 DOMDoc 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 DOMDoc, and based on these observations, apply Extract Interface, too.
1 | <?php namespace BetterDOMDocument; |
||
11 | class DOMDoc extends \DOMDocument { |
||
12 | |||
13 | private $auto_ns = FALSE; |
||
14 | public $ns = array(); |
||
15 | public $default_ns = FALSE; |
||
16 | public $error_checking = 'strict'; // Can be 'strict', 'warning', 'none' / FALSE |
||
17 | |||
18 | /** |
||
19 | * Create a new DOMDoc |
||
20 | * |
||
21 | * @param mixed $xml |
||
22 | * $xml can either be an XML string, a DOMDocument, or a DOMElement. |
||
23 | * You can also pass FALSE or NULL (or omit it) and load XML later using loadXML or loadHTML |
||
24 | * |
||
25 | * @param mixed $auto_register_namespaces |
||
26 | * Auto-register namespaces. All namespaces in the root element will be registered for use in xpath queries. |
||
27 | * Namespaces that are not declared in the root element will not be auto-registered |
||
28 | * Defaults to TRUE (Meaning it will auto register all auxiliary namespaces but not the default namespace). |
||
29 | * Pass a prefix string to automatically register the default namespace. |
||
30 | * Pass FALSE to disable auto-namespace registeration |
||
31 | * |
||
32 | * @param bool $error_checking |
||
33 | * Can be 'strict', 'warning', or 'none. Defaults to 'strict'. |
||
34 | * 'none' supresses all errors |
||
35 | * 'warning' is the default behavior in DOMDocument |
||
36 | * 'strict' corresponds to DOMDocument strictErrorChecking TRUE |
||
37 | */ |
||
38 | 24 | public function __construct($xml = FALSE, $auto_register_namespaces = TRUE, $error_checking = 'strict') { |
|
72 | |||
73 | /** |
||
74 | * Register a namespace to be used in xpath queries |
||
75 | * |
||
76 | * @param string $prefix |
||
77 | * Namespace prefix to register |
||
78 | * |
||
79 | * @param string $url |
||
80 | * Connonical URL for this namespace prefix |
||
81 | */ |
||
82 | 13 | public function registerNamespace($prefix, $url) { |
|
85 | |||
86 | /** |
||
87 | * Get the list of registered namespaces as an array |
||
88 | */ |
||
89 | 7 | public function getNamespaces() { |
|
92 | |||
93 | /** |
||
94 | * Given a namespace URL, get the prefix |
||
95 | * |
||
96 | * @param string $url |
||
97 | * Connonical URL for this namespace prefix |
||
98 | * |
||
99 | * @return string|false |
||
100 | * The namespace prefix or FALSE if there is no namespace with that URL |
||
101 | */ |
||
102 | 1 | public function lookupPrefix($url) { |
|
105 | |||
106 | /** |
||
107 | * Given a namespace prefix, get the URL |
||
108 | * |
||
109 | * @param string $prefix |
||
110 | * namespace prefix |
||
111 | * |
||
112 | * return string|false |
||
113 | * The namespace URL or FALSE if there is no namespace with that prefix |
||
114 | */ |
||
115 | 1 | public function lookupURL($prefix) { |
|
123 | |||
124 | /** |
||
125 | * Given an xpath, get a list of nodes. |
||
126 | * |
||
127 | * @param string $xpath |
||
128 | * xpath to be used for query |
||
129 | * |
||
130 | * @param mixed $context |
||
131 | * $context can either be an xpath string, or a DOMElement |
||
132 | * Provides context for the xpath query |
||
133 | * |
||
134 | * @return DOMList|false |
||
135 | * A DOMList object, which is very similar to a DOMNodeList, but with better iterabilility. |
||
136 | */ |
||
137 | 18 | public function xpath($xpath, $context = NULL) { |
|
165 | |||
166 | |||
167 | /** |
||
168 | * Given an xpath, get a single node (first one found) |
||
169 | * |
||
170 | * @param string $xpath |
||
171 | * xpath to be used for query |
||
172 | * |
||
173 | * @param mixed $context |
||
174 | * $context can either be an xpath string, or a DOMElement |
||
175 | * Provides context for the xpath query |
||
176 | * |
||
177 | * @return mixed |
||
178 | * The first node found by the xpath query |
||
179 | */ |
||
180 | 18 | public function xpathSingle($xpath, $context = NULL) { |
|
190 | |||
191 | |||
192 | /** |
||
193 | * Given an CSS selector, get a list of nodes. |
||
194 | * |
||
195 | * @param string $css_selector |
||
196 | * CSS Selector to be used for query |
||
197 | * |
||
198 | * @param mixed $context |
||
199 | * $context can either be an xpath string, or a DOMElement |
||
200 | * Provides context for the CSS selector |
||
201 | * |
||
202 | * @return DOMList|false |
||
203 | * A DOMList object, which is very similar to a DOMNodeList, but with better iterabilility. |
||
204 | */ |
||
205 | 1 | public function select($css_selector, $context = NULL) { |
|
211 | |||
212 | /** |
||
213 | * Given an CSS selector, get a single node. |
||
214 | * |
||
215 | * @param string $css_selector |
||
216 | * CSS Selector to be used for query |
||
217 | * |
||
218 | * @param mixed $context |
||
219 | * $context can either be an xpath string, or a DOMElement |
||
220 | * Provides context for the CSS selector |
||
221 | * |
||
222 | * @return DOMList |
||
223 | * A DOMList object, which is very similar to a DOMNodeList, but with better iterabilility. |
||
224 | */ |
||
225 | 1 | public function selectSingle($css_selector, $context = NULL) { |
|
231 | |||
232 | /** |
||
233 | * Get the document (or an element) as an array |
||
234 | * |
||
235 | * @param string $raw |
||
236 | * Can be either FALSE, 'full', or 'inner'. Defaults to FALSE. |
||
237 | * When set to 'full' every node's full XML is also attached to the array |
||
238 | * When set to 'inner' every node's inner XML is attached to the array. |
||
239 | * |
||
240 | * @param mixed $context |
||
241 | * Optional context node. Can pass an DOMElement object or an xpath string. |
||
242 | * If passed, only the given node will be used when generating the array |
||
243 | */ |
||
244 | 1 | public function getArray($raw = FALSE, $context = NULL) { |
|
289 | |||
290 | /** |
||
291 | * Get the inner text of an element |
||
292 | * |
||
293 | * @param mixed $context |
||
294 | * Optional context node. Can pass an DOMElement object or an xpath string. |
||
295 | */ |
||
296 | 1 | public function innerText($context = NULL) { |
|
308 | |||
309 | /** |
||
310 | * Create an DOMElement from XML and attach it to the DOMDocument |
||
311 | * |
||
312 | * Note that this does not place it anywhere in the dom tree, it merely imports it. |
||
313 | * |
||
314 | * @param string $xml |
||
315 | * XML string to import |
||
316 | */ |
||
317 | 5 | public function createElementFromXML($xml) { |
|
349 | |||
350 | /** |
||
351 | * Append a child to the context node, make it the last child |
||
352 | * |
||
353 | * @param mixed $newnode |
||
354 | * $newnode can either be an XML string, a DOMDocument, or a DOMElement. |
||
355 | * |
||
356 | * @param mixed $context |
||
357 | * $context can either be an xpath string, or a DOMElement |
||
358 | * Omiting $context results in using the root document element as the context |
||
359 | * |
||
360 | * @return DOMElement|false |
||
361 | * The $newnode, properly attached to DOMDocument. If you passed $newnode as a DOMElement |
||
362 | * then you should replace your DOMElement with the returned one. |
||
363 | */ |
||
364 | 1 | public function append($newnode, $context = NULL) { |
|
390 | |||
391 | /** |
||
392 | * Append a child to the context node, make it the first child |
||
393 | * |
||
394 | * @param mixed $newnode |
||
395 | * $newnode can either be an XML string, a DOMDocument, or a DOMElement. |
||
396 | * |
||
397 | * @param mixed $context |
||
398 | * $context can either be an xpath string, or a DOMElement |
||
399 | * Omiting $context results in using the root document element as the context |
||
400 | * |
||
401 | * @return DOMElement|false |
||
402 | * The $newnode, properly attached to DOMDocument. If you passed $newnode as a DOMElement |
||
403 | * then you should replace your DOMElement with the returned one. |
||
404 | */ |
||
405 | 1 | View Code Duplication | public function prepend($newnode, $context = NULL) { |
415 | |||
416 | /** |
||
417 | * Prepend a sibling to the context node, put it just before the context node |
||
418 | * |
||
419 | * @param mixed $newnode |
||
420 | * $newnode can either be an XML string, a DOMDocument, or a DOMElement. |
||
421 | * |
||
422 | * @param mixed $context |
||
423 | * $context can either be an xpath string, or a DOMElement |
||
424 | * Omiting $context results in using the root document element as the context |
||
425 | * |
||
426 | * @return DOMElement|false |
||
427 | * The $newnode, properly attached to DOMDocument. If you passed $newnode as a DOMElement |
||
428 | * then you should replace your DOMElement with the returned one. |
||
429 | */ |
||
430 | 1 | View Code Duplication | public function prependSibling($newnode, $context = NULL) { |
440 | |||
441 | /** |
||
442 | * Append a sibling to the context node, put it just after the context node |
||
443 | * |
||
444 | * @param mixed $newnode |
||
445 | * $newnode can either be an XML string, a DOMDocument, or a DOMElement. |
||
446 | * |
||
447 | * @param mixed $context |
||
448 | * $context can either be an xpath string, or a DOMElement |
||
449 | * Omiting $context results in using the root document element as the context |
||
450 | * |
||
451 | * @return DOMElement|false |
||
452 | * The $newnode, properly attached to DOMDocument. If you passed $newnode as a DOMElement |
||
453 | * then you should replace your DOMElement with the returned one. |
||
454 | */ |
||
455 | 1 | public function appendSibling($newnode, $context) { |
|
487 | |||
488 | /** |
||
489 | * Given an xpath or DOMElement, return a new DOMDoc. |
||
490 | * |
||
491 | * @param mixed $node |
||
492 | * $node can either be an xpath string or a DOMElement. |
||
493 | * |
||
494 | * @return DOMDoc |
||
495 | * A new DOMDoc created from the xpath or DOMElement |
||
496 | */ |
||
497 | 7 | public function extract($node, $auto_register_namespaces = TRUE, $error_checking = 'none') { |
|
503 | |||
504 | /** |
||
505 | * Given a pair of nodes, replace the first with the second |
||
506 | * |
||
507 | * @param mixed $node |
||
508 | * Node to be replaced. Can either be an xpath string or a DOMDocument (or even a DOMNode). |
||
509 | * |
||
510 | * @param mixed $replace |
||
511 | * Replace $node with $replace. Replace can be an XML string, or a DOMNode |
||
512 | * |
||
513 | * @return mixed |
||
514 | * The overwritten / replaced node. |
||
515 | */ |
||
516 | 2 | public function replace($node, $replace) { |
|
531 | |||
532 | /** |
||
533 | * Given a node(s), remove / delete them |
||
534 | * |
||
535 | * @param mixed $node |
||
536 | * Can pass a DOMNode, a NodeList, DOMNodeList, an xpath string, or an array of any of these. |
||
537 | */ |
||
538 | 1 | public function remove($node) { |
|
559 | |||
560 | /** |
||
561 | * Given an XSL string, transform the DOMDoc (or a passed context node) |
||
562 | * |
||
563 | * @param string $xsl |
||
564 | * XSL Transormation |
||
565 | * |
||
566 | * @param mixed $context |
||
567 | * $context can either be an xpath string, or a DOMElement. Ommiting it |
||
568 | * results in transforming the entire document |
||
569 | * |
||
570 | * @return a new DOMDoc |
||
571 | */ |
||
572 | 4 | public function tranform($xsl, $context = NULL) { |
|
589 | |||
590 | /** |
||
591 | * Given a node, change it's namespace to the specified namespace in situ |
||
592 | * |
||
593 | * @param mixed $node |
||
594 | * Node to be changed. Can either be an xpath string or a DOMElement. |
||
595 | * |
||
596 | * @param mixed $prefix |
||
597 | * prefix for the new namespace |
||
598 | * |
||
599 | * @param mixed $url |
||
600 | * The URL for the new namespace |
||
601 | * |
||
602 | * @return mixed |
||
603 | * The node with the new namespace. The node will also be changed in-situ in the document as well. |
||
604 | */ |
||
605 | 1 | public function changeNamespace($node, $prefix, $url) { |
|
633 | |||
634 | /** |
||
635 | * Get a lossless HTML representation of the XML |
||
636 | * |
||
637 | * Transforms the document (or passed context) into a set of HTML spans. |
||
638 | * The element name becomes the class, all other attributes become HTML5 |
||
639 | * "data-" attributes. |
||
640 | * |
||
641 | * @param mixed $context |
||
642 | * $context can either be an xpath string, or a DOMElement. Ommiting it |
||
643 | * results in transforming the entire document |
||
644 | * |
||
645 | * @param array $options |
||
646 | * Options for transforming the HTML into XML. The following options are supported: |
||
647 | * 'xlink' => {TRUE or xpath} |
||
648 | * Transform xlink links into <a href> elements. If you specify 'xlink' => TRUE then |
||
649 | * it will transform all elements with xlink:type = simple into a <a href> element. |
||
650 | * Alternatively you may specify your own xpath for selecting which elements get transformed |
||
651 | * into <a href> tags. |
||
652 | * @return HTML string |
||
653 | */ |
||
654 | 3 | public function asHTML($context = NULL, $options = array()) { |
|
726 | |||
727 | /** |
||
728 | * Output the DOMDoc as an XML string |
||
729 | * |
||
730 | * @param mixed $context |
||
731 | * $context can either be an xpath string, or a DOMElement. Ommiting it |
||
732 | * results in outputting the entire document |
||
733 | * |
||
734 | * @return XML string |
||
735 | */ |
||
736 | 12 | public function out($context = NULL) { |
|
761 | |||
762 | /** |
||
763 | * Magic method for casting a DOMDoc as a string |
||
764 | */ |
||
765 | 1 | public function __toString() { |
|
768 | |||
769 | 24 | public function setErrorChecking($error_checking) { |
|
781 | |||
782 | 14 | public static function loadFile($file_or_url, $auto_register_namespaces = TRUE) { |
|
790 | |||
791 | 1 | public function loadHTML($source, $options = NULL) { |
|
797 | |||
798 | 14 | public function loadXML($source, $options = NULL) { |
|
804 | |||
805 | 24 | private function AutoRegisterNamespace($auto_register_namespaces) { |
|
855 | |||
856 | 22 | private function createContext(&$context, $type = 'xpath', $createDocument = TRUE) { |
|
886 | } |
||
887 | |||
890 |
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.