diff --git a/stubs/dom.stub b/stubs/dom.stub index d2a5c575fc..b219756b1d 100644 --- a/stubs/dom.stub +++ b/stubs/dom.stub @@ -1,6 +1,6 @@ |null + */ + public $attributes; + /** + * @phpstan-assert-if-true DOMNamedNodeMap $this->attributes + */ + public function hasAttributes(): bool {} } class DOMElement extends DOMNode @@ -38,6 +47,12 @@ class DOMElement extends DOMNode /** @var DOMDocument */ public $ownerDocument; + /** + * @readonly + * @var DOMNamedNodeMap + */ + public $attributes; + /** * @param string $name * @return DOMNodeList @@ -50,7 +65,6 @@ class DOMElement extends DOMNode * @return DOMNodeList */ public function getElementsByTagNameNS ($namespaceURI, $localName) {} - } /** @@ -82,7 +96,7 @@ class DOMXPath } -class DOMAttr +class DOMAttr extends DOMNode { /** @var DOMDocument */ @@ -90,7 +104,7 @@ class DOMAttr } -class DOMCharacterData +class DOMCharacterData extends DOMNode { /** @var DOMDocument */ @@ -98,7 +112,7 @@ class DOMCharacterData } -class DOMDocumentType +class DOMDocumentType extends DOMNode { /** @var DOMDocument */ @@ -106,7 +120,7 @@ class DOMDocumentType } -class DOMEntity +class DOMEntity extends DOMNode { /** @var DOMDocument */ @@ -114,7 +128,7 @@ class DOMEntity } -class DOMNotation +class DOMNotation extends DOMNode { /** @var DOMDocument */ @@ -122,7 +136,7 @@ class DOMNotation } -class DOMProcessingInstruction +class DOMProcessingInstruction extends DOMNode { /** @var DOMDocument */ @@ -141,14 +155,35 @@ class DOMProcessingInstruction } /** + * @template-covariant TNode as DOMNode + * @implements Traversable + * @implements IteratorAggregate * @property-read int $length */ -class DOMNamedNodeMap +class DOMNamedNodeMap implements Traversable, IteratorAggregate, Countable { + /** + * @return (TNode | null) + */ + public function getNamedItem(string $qualifiedName) + { + } + /** + * @return (TNode | null) + */ + public function getNamedItemNS(?string $namespace, string $localName) + { + } + /** + * @return (TNode | null) + */ + public function item(int $index) + { + } } -class DOMText +class DOMText extends DOMCharacterData { /** @var string */ diff --git a/tests/PHPStan/Analyser/nsrt/DOMLegacyNamedNodeNap.php b/tests/PHPStan/Analyser/nsrt/DOMLegacyNamedNodeNap.php new file mode 100644 index 0000000000..8d2a406f8c --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/DOMLegacyNamedNodeNap.php @@ -0,0 +1,48 @@ +hasAttributes()) { + assertType('DOMNamedNodeMap', $node->attributes); + } else { + assertType('null', $node->attributes); + } + } + + public function element_node(DOMElement $element): void + { + assertType('DOMNamedNodeMap', $element->attributes); + if ($element->hasAttribute('class')) { + $attribute = $element->getAttributeNode('class'); + assertType(DOMAttr::class, $attribute); + assertType('string', $attribute->value); + } else { + $attribute = $element->getAttributeNode('class'); + assertType('false', $attribute); + } + } + + public function element_node_attribute_fetch_via_attributes_property(DOMElement $element): void + { + assertType('DOMNamedNodeMap', $element->attributes); + if ($element->hasAttribute('class')) { + $attribute = $element->attributes->getNamedItem('class'); + if ($attribute === null) { + return; + } + assertType(DOMAttr::class, $attribute); + assertType('string', $attribute->value); + } + } +} diff --git a/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php index a07611980b..e33dbd7b4e 100644 --- a/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php @@ -957,4 +957,12 @@ public function testTraitMixin(): void $this->analyse([__DIR__ . '/data/trait-mixin.php'], []); } + public function testDomExtensionLegacyTemplateNodes(): void + { + $this->checkThisOnly = false; + $this->checkUnionTypes = true; + $this->checkDynamicProperties = true; + $this->analyse([__DIR__ . '/data/dom-legacy-ext-template-nodes.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Properties/data/dom-legacy-ext-template-nodes.php b/tests/PHPStan/Rules/Properties/data/dom-legacy-ext-template-nodes.php new file mode 100644 index 0000000000..262510c714 --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/dom-legacy-ext-template-nodes.php @@ -0,0 +1,33 @@ +attributes); +} + +function element_node(\DOMElement $element): void +{ + if ($element->hasAttribute('class')) { + $attribute = $element->getAttributeNode('class'); + echo $attribute->value; + } +} + +function element_node_attribute_fetch_via_attributes_property(\DOMElement $element): void +{ + $attribute = $element->attributes->getNamedItem('class'); + if ($attribute === null) { + return; + } + echo $attribute->value; +} + +function element_node_attribute_fetch_via_getAttributeNode(\DOMElement $element): void +{ + $attribute = $element->getAttributeNode('class'); + if ($attribute === null) { + return; + } + echo $attribute->value; +}