Passed
Push — master ( 7bbd81...0263a7 )
by Josh
20:33 queued 09:30
created

Document::needsWorkarounds()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 3

Importance

Changes 0
Metric Value
eloc 5
c 0
b 0
f 0
dl 0
loc 15
rs 10
ccs 4
cts 4
cp 1
cc 3
nc 3
nop 0
crap 3
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
48
		$classes   = ['Attr', 'CdataSection', 'Comment', 'DocumentFragment', 'Element', 'Text'];
49
		$namespace = $this->getNodesNamespace();
50
		foreach ($classes as $className)
51
		{
52
			$this->registerNodeClass('DOM' . $className, $namespace . '\\' . $className);
53
		}
54 2
	}
55
56 2
	/**
57 2
	* Evaluate and return the result of a given XPath expression
58
	*/
59 2
	public function evaluate(string $expression, ?DOMNode $contextNode = null, bool $registerNodeNS = true): mixed
60
	{
61
		return $this->xpath('evaluate', func_get_args());
62
	}
63
64
	/**
65
	* Evaluate and return the first element of a given XPath query
66
	*/
67 1
	public function firstOf(string $expression, ?DOMNode $contextNode = null, bool $registerNodeNS = true): ?DOMNode
68
	{
69 1
		return $this->query(...func_get_args())->item(0);
70
	}
71
72
	/**
73
	* Evaluate and return the result of a given XPath query
74
	*/
75
	public function query(string $expression, ?DOMNode $contextNode = null, bool $registerNodeNS = true): DOMNodeList
76
	{
77
		$result = $this->xpath('query', func_get_args());
78 2
		if ($result === false)
79
		{
80 2
			$errorMessage = libxml_get_last_error()?->message ?? 'No error message';
81
82
			throw new RuntimeException('Invalid XPath query: ' . trim($errorMessage));
83
		}
84
85
		return $result;
86
	}
87
88
	protected function getNodesNamespace(): string
89 1
	{
90
		$namespace = __NAMESPACE__;
91 1
		if ($this->needsWorkarounds())
92 1
		{
93
			$namespace .= '\\PatchedNodes';
94 1
		}
95
		elseif (version_compare(PHP_VERSION, '8.3.0', '<'))
96
		{
97
			$namespace .= '\\ForwardCompatibleNodes';
98
		}
99
100
		return $namespace;
101
	}
102
103
	protected function needsWorkarounds(): bool
104 2
	{
105
		if (version_compare(PHP_VERSION, '8.2.10', '>='))
106 2
		{
107 2
			// PHP ^8.2.10 needs no workarounds
108
			return false;
109 2
		}
110
		if (version_compare(PHP_VERSION, '8.1.23', '<'))
111
		{
112
			// Anything older than 8.1.23 does
113
			return true;
114
		}
115
116
		// ~8.1.23 is okay, anything between 8.2.0 and 8.2.9 needs workarounds
117
		return version_compare(PHP_VERSION, '8.2.0-dev', '>=');
1 ignored issue
show
Bug Best Practice introduced by
The expression return version_compare(P...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...
118 2
	}
119
120 2
	/**
121
	* Execute a DOMXPath method and return the result
122
	*/
123
	protected function xpath(string $methodName, array $args): mixed
124
	{
125
		$xpath = new DOMXPath($this);
126
		$xpath->registerNamespace('xsl', 'http://www.w3.org/1999/XSL/Transform');
127
		$xpath->registerNodeNamespaces = true;
128
129 2
		return $xpath->$methodName(...$args);
130
	}
131
}