Passed
Push — master ( d992e6...b07ae8 )
by Josh
01:26
created

NodeComparator   A

Complexity

Total Complexity 40

Size/Duplication

Total Lines 151
Duplicated Lines 0 %

Importance

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