Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mark DOMNamedNodeMap as taking a covariant DOMNode #3714

Open
wants to merge 14 commits into
base: 1.12.x
Choose a base branch
from
55 changes: 45 additions & 10 deletions stubs/dom.stub
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php

class DOMDocument
class DOMDocument extends DOMNode
{

/** @var DOMDocumentType|null */
Expand Down Expand Up @@ -29,7 +29,16 @@ class DOMDocument

class DOMNode
{
/**
* @readonly
* @var DOMNamedNodeMap<DOMAttr>|null
*/
public $attributes;

/**
* @phpstan-assert-if-true DOMNamedNodeMap<DOMAttr> $this->attributes
*/
public function hasAttributes(): bool {}
}

class DOMElement extends DOMNode
Expand All @@ -38,6 +47,12 @@ class DOMElement extends DOMNode
/** @var DOMDocument */
public $ownerDocument;

/**
* @readonly
* @var DOMNamedNodeMap<DOMAttr>
*/
public $attributes;

/**
* @param string $name
* @return DOMNodeList<DOMElement>
Expand All @@ -50,7 +65,6 @@ class DOMElement extends DOMNode
* @return DOMNodeList<DOMElement>
*/
public function getElementsByTagNameNS ($namespaceURI, $localName) {}

}

/**
Expand Down Expand Up @@ -82,47 +96,47 @@ class DOMXPath

}

class DOMAttr
class DOMAttr extends DOMNode
{

/** @var DOMDocument */
public $ownerDocument;

}

class DOMCharacterData
class DOMCharacterData extends DOMNode
{

/** @var DOMDocument */
public $ownerDocument;

}

class DOMDocumentType
class DOMDocumentType extends DOMNode
{

/** @var DOMDocument */
public $ownerDocument;

}

class DOMEntity
class DOMEntity extends DOMNode
{

/** @var DOMDocument */
public $ownerDocument;

}

class DOMNotation
class DOMNotation extends DOMNode
{

/** @var DOMDocument */
public $ownerDocument;

}

class DOMProcessingInstruction
class DOMProcessingInstruction extends DOMNode
{

/** @var DOMDocument */
Expand All @@ -141,14 +155,35 @@ class DOMProcessingInstruction
}

/**
* @template-covariant TNode as DOMNode
* @implements Traversable<string, TNode>
* @implements IteratorAggregate<int, TNode>
* @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 */
Expand Down
48 changes: 48 additions & 0 deletions tests/PHPStan/Analyser/nsrt/DOMLegacyNamedNodeNap.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

declare(strict_types = 1);

namespace DOMLegacyNamedNodeMap;

use DOMAttr;
use DOMElement;
use DOMNode;
use DOMNamedNodeMap;
use function PHPStan\Testing\assertType;

class Foo
{
public function basic_node(DOMNode $node): void {
if ($node->hasAttributes()) {
assertType('DOMNamedNodeMap<DOMAttr>', $node->attributes);
} else {
assertType('null', $node->attributes);
}
}

public function element_node(DOMElement $element): void
{
assertType('DOMNamedNodeMap<DOMAttr>', $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<DOMAttr>', $element->attributes);
if ($element->hasAttribute('class')) {
$attribute = $element->attributes->getNamedItem('class');
if ($attribute === null) {
return;
}
assertType(DOMAttr::class, $attribute);
assertType('string', $attribute->value);
}
}
}
8 changes: 8 additions & 0 deletions tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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'], []);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

namespace DOMNodeStubsAccessProperties;

function basic_node(\DOMNode $node): void {
var_dump($node->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;
}
Loading