Passed
Push — master ( f6dff3...d992e6 )
by Josh
11:44
created

Document::getExtendedClassMap()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 6
c 0
b 0
f 0
dl 0
loc 11
ccs 4
cts 4
cp 1
rs 10
cc 2
nc 2
nop 0
crap 2
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 DOMDocument;
11
use DOMNode;
12
use DOMNodeList;
13
use DOMXPath;
14
use RuntimeException;
15
use const PHP_VERSION;
16
use function func_get_args, libxml_get_last_error, trim, version_compare;
17
18
/**
19
* @method Attr|false createAttribute(string $localName)
20
* @method Attr|false createAttributeNS(?string $namespace, string $qualifiedName)
21
* @method CdataSection|false createCDATASection(string $data)
22
* @method Comment createComment(string $data)
23 26
* @method DocumentFragment createDocumentFragment()
24
* @method Element|false createElement(string $localName, string $value = '')
25 26
* @method Element|false createElementNS(?string $namespace, string $qualifiedName, string $value = '')
26
* @method Text createTextNode(string $data)
27 26
* @method ?Element getElementById(string $elementId)
28
* @property ?DocumentType $doctype
29
* @property ?Element $documentElement
30
* @property ?Element $firstElementChild
31
* @property ?Element $lastElementChild
32
* @property ?Document $ownerDocument
33
* @property ?Element $parentElement
34
*/
35
class Document extends DOMDocument
36 2
{
37
	public NodeCreator $nodeCreator;
38 2
39 2
	/**
40
	* @link https://www.php.net/manual/domdocument.construct.php
41 1
	*/
42
	public function __construct(string $version = '1.0', string $encoding = '')
43
	{
44 2
		parent::__construct($version, $encoding);
45
46
		$this->nodeCreator = new NodeCreator($this);
47
		foreach ($this->getExtendedClassMap() as $baseClass => $extendedClass)
48
		{
49
			$this->registerNodeClass($baseClass, $extendedClass);
50
		}
51
	}
52
53
	/**
54 2
	* Evaluate and return the result of a given XPath expression
55
	*/
56 2
	public function evaluate(string $expression, ?DOMNode $contextNode = null, bool $registerNodeNS = true): mixed
57 2
	{
58
		return $this->xpath('evaluate', func_get_args());
59 2
	}
60
61
	/**
62
	* Evaluate and return the first element of a given XPath query
63
	*/
64
	public function firstOf(string $expression, ?DOMNode $contextNode = null, bool $registerNodeNS = true): ?DOMNode
65
	{
66
		return $this->query(...func_get_args())->item(0);
67 1
	}
68
69 1
	/**
70
	* Evaluate and return the result of a given XPath query
71
	*/
72
	public function query(string $expression, ?DOMNode $contextNode = null, bool $registerNodeNS = true): DOMNodeList
73
	{
74
		$result = $this->xpath('query', func_get_args());
75
		if ($result === false)
76
		{
77
			$errorMessage = libxml_get_last_error()?->message ?? 'No error message';
78 2
79
			throw new RuntimeException('Invalid XPath query: ' . trim($errorMessage));
80 2
		}
81
82
		return $result;
83
	}
84
85
	protected function getExtendedClassMap(): array
86
	{
87
		$baseNames = ['Attr', 'CdataSection', 'Comment', 'DocumentFragment', 'Element', 'Text'];
88
		$classMap  = [];
89 1
		$namespace = $this->getExtendedNamespace(PHP_VERSION);
90
		foreach ($baseNames as $baseName)
91 1
		{
92 1
			$classMap['DOM' . $baseName] = $namespace . '\\' . $baseName;
93
		}
94 1
95
		return $classMap;
96
	}
97
98
	protected function getExtendedNamespace(string $phpVersion): string
99
	{
100
		$namespace = __NAMESPACE__;
101
		if ($this->needsWorkarounds($phpVersion))
102
		{
103
			$namespace .= '\\PatchedNodes';
104 2
		}
105
		elseif (version_compare($phpVersion, '8.3.0', '<'))
106 2
		{
107 2
			$namespace .= '\\ForwardCompatibleNodes';
108
		}
109 2
110
		return $namespace;
111
	}
112
113
	protected function needsWorkarounds(string $phpVersion): bool
114
	{
115
		if (version_compare($phpVersion, '8.2.10', '>='))
116
		{
117
			// PHP ^8.2.10 needs no workarounds
118 2
			return false;
119
		}
120 2
		if (version_compare($phpVersion, '8.1.23', '<'))
121
		{
122
			// Anything older than 8.1.23 does
123
			return true;
124
		}
125
126
		// ~8.1.23 is okay, anything between 8.2.0 and 8.2.9 needs workarounds
127
		return version_compare($phpVersion, '8.2.0-dev', '>=');
1 ignored issue
show
Bug Best Practice introduced by
The expression return version_compare($...ion, '8.2.0-dev', '>=') could return the type integer which is incompatible with the type-hinted return boolean. Consider adding an additional type-check to rule them out.
Loading history...
128
	}
129 2
130
	/**
131 2
	* Execute a DOMXPath method and return the result
132
	*/
133
	protected function xpath(string $methodName, array $args): mixed
134
	{
135
		$xpath = new DOMXPath($this);
136
		$xpath->registerNamespace('xsl', 'http://www.w3.org/1999/XSL/Transform');
137
		$xpath->registerNodeNamespaces = true;
138
139
		return $xpath->$methodName(...$args);
140
	}
141
}