NodeComparator   A
last analyzed

Complexity

Total Complexity 40

Size/Duplication

Total Lines 150
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 40
eloc 58
c 2
b 0
f 0
dl 0
loc 150
rs 9.2

14 Methods

Rating   Name   Duplication   Size   Complexity  
B isEqualElement() 0 20 8
A isEqualDocumentFragment() 0 3 1
A isEqualDocument() 0 3 1
A getNamespaceDeclarations() 0 13 3
A isEqualNode() 0 16 6
A isEqualNotation() 0 5 3
A isEqualEntityReference() 0 3 1
A isEqualEntity() 0 5 3
A isEqualCharacterData() 0 4 1
A isEqualDocumentType() 0 5 3
A isEqualProcessingInstruction() 0 3 2
A isEqualAttr() 0 5 3
A isEqualNodeList() 0 15 4
A hasEqualNamespaceDeclarations() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like NodeComparator 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.

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 NodeComparator, and based on these observations, apply Extract Interface, too.

1
<?php declare(strict_types=1);
2
3
/**
4
* @package   s9e\SweetDOM
5
* @copyright Copyright (c) The s9e authors
6
* @license   http://www.opensource.org/licenses/mit-license.php The MIT License
7
*/
8
namespace s9e\SweetDOM;
9
10
use DOMAttr;
11
use DOMCharacterData;
12
use DOMDocument;
13
use DOMDocumentFragment;
14
use DOMDocumentType;
15
use DOMElement;
16
use DOMEntity;
17
use DOMEntityReference;
18
use DOMNode;
19
use DOMNodeList;
20
use DOMNotation;
21
use DOMProcessingInstruction;
22
use DOMXPath;
23
24
class NodeComparator
25
{
26
	protected static $classMap = [
27
		'DOMElement'               => 'isEqualElement',
28
		'DOMCharacterData'         => 'isEqualCharacterData',
29
		'DOMProcessingInstruction' => 'isEqualProcessingInstruction',
30
		'DOMAttr'                  => 'isEqualAttr',
31
		'DOMDocument'              => 'isEqualDocument',
32
		'DOMDocumentFragment'      => 'isEqualDocumentFragment',
33
		'DOMDocumentType'          => 'isEqualDocumentType',
34
		'DOMEntityReference'       => 'isEqualEntityReference',
35
		'DOMEntity'                => 'isEqualEntity',
36
		'DOMNotation'              => 'isEqualNotation'
37
	];
38
39
	// https://dom.spec.whatwg.org/#concept-node-equals
40
	// https://github.com/php/php-src/blob/master/ext/dom/node.c
41
	public static function isEqualNode(?DOMNode $node, ?DOMNode $otherNode): bool
42
	{
43
		if (!isset($node, $otherNode) || $node->nodeType !== $otherNode->nodeType)
44
		{
45
			return false;
46
		}
47
		foreach (static::$classMap as $className => $methodName)
48
		{
49
			if ($node instanceof $className && $otherNode instanceof $className)
50
			{
51
				return static::$methodName($node, $otherNode);
52
			}
53
		}
54
55
		// @codeCoverageIgnoreStart
56
		return $node->isSameNode($otherNode);
57
		// @codeCoverageIgnoreEnd
58
	}
59
60
	/**
61
	* @return array<string, string>
62
	*/
63
	protected static function getNamespaceDeclarations(DOMElement $element): array
64
	{
65
		$namespaces = [];
66
		$xpath      = new DOMXPath($element->ownerDocument);
67
		foreach ($xpath->query('namespace::*', $element) as $node)
68
		{
69
			if ($element->hasAttribute($node->nodeName))
70
			{
71
				$namespaces[$node->nodeName] = $node->nodeValue;
72
			}
73
		}
74
75
		return $namespaces;
76
	}
77
78
	protected static function hasEqualNamespaceDeclarations(DOMElement $element, DOMElement $otherElement): bool
79
	{
80
		return static::getNamespaceDeclarations($element) == static::getNamespaceDeclarations($otherElement);
81
	}
82
83
	protected static function isEqualAttr(DOMAttr $node, DOMAttr $otherNode): bool
84
	{
85
		return $node->namespaceURI === $otherNode->namespaceURI
86
		    && $node->localName    === $otherNode->localName
87
		    && $node->value        === $otherNode->value;
88
	}
89
90
	protected static function isEqualCharacterData(DOMCharacterData $node, DOMCharacterData $otherNode): bool
91
	{
92
		// Covers DOMCdataSection, DOMComment, and DOMText
93
		return $node->data === $otherNode->data;
94
	}
95
96
	protected static function isEqualDocument(DOMDocument $node, DOMDocument $otherNode): bool
97
	{
98
		return static::isEqualNodeList($node->childNodes, $otherNode->childNodes);
99
	}
100
101
	protected static function isEqualDocumentFragment(DOMDocumentFragment $node, DOMDocumentFragment $otherNode): bool
102
	{
103
		return static::isEqualNodeList($node->childNodes, $otherNode->childNodes);
104
	}
105
106
	protected static function isEqualDocumentType(DOMDocumentType $node, DOMDocumentType $otherNode): bool
107
	{
108
		return $node->name     === $otherNode->name
109
		    && $node->publicId === $otherNode->publicId
110
		    && $node->systemId === $otherNode->systemId;
111
	}
112
113
	protected static function isEqualElement(DOMElement $element, DOMElement $otherElement): bool
114
	{
115
		if ($element->namespaceURI       !== $otherElement->namespaceURI
116
		 || $element->nodeName           !== $otherElement->nodeName
117
		 || $element->attributes->length !== $otherElement->attributes->length
118
		 || $element->childNodes->length !== $otherElement->childNodes->length)
119
		{
120
			return false;
121
		}
122
123
		foreach ($element->attributes as $attribute)
124
		{
125
			if ($attribute->value !== $otherElement->attributes->getNamedItem($attribute->name)?->value)
126
			{
127
				return false;
128
			}
129
		}
130
131
		return static::isEqualNodeList($element->childNodes, $otherElement->childNodes)
132
		    && static::hasEqualNamespaceDeclarations($element, $otherElement);
133
	}
134
135
	protected static function isEqualEntity(DOMEntity $node, DOMEntity $otherNode): bool
136
	{
137
		return $node->nodeName === $otherNode->nodeName
138
		    && $node->publicId === $otherNode->publicId
139
		    && $node->systemId === $otherNode->systemId;
140
	}
141
142
	protected static function isEqualEntityReference(DOMEntityReference $node, DOMEntityReference $otherNode): bool
143
	{
144
		return $node->nodeName === $otherNode->nodeName;
145
	}
146
147
	protected static function isEqualNodeList(DOMNodeList $list, DOMNodeList $otherList): bool
148
	{
149
		if ($list->length !== $otherList->length)
150
		{
151
			return false;
152
		}
153
		foreach ($list as $i => $node)
154
		{
155
			if (!static::isEqualNode($node, $otherList->item($i)))
156
			{
157
				return false;
158
			}
159
		}
160
161
		return true;
162
	}
163
164
	protected static function isEqualNotation(DOMNotation $node, DOMNotation $otherNode): bool
165
	{
166
		return $node->nodeName === $otherNode->nodeName
167
		    && $node->publicId === $otherNode->publicId
168
		    && $node->systemId === $otherNode->systemId;
169
	}
170
171
	protected static function isEqualProcessingInstruction(DOMProcessingInstruction $node, DOMProcessingInstruction $otherNode): bool
172
	{
173
		return $node->target === $otherNode->target && $node->data === $otherNode->data;
174
	}
175
}