Skip to content

Commit a73fec1

Browse files
committed
Make DOMNamedNodeMap generic
Resolves phpstan/phpstan#13365.
1 parent c0e6f0e commit a73fec1

File tree

3 files changed

+62
-9
lines changed

3 files changed

+62
-9
lines changed

stubs/dom.stub

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,12 @@ class DOMNode
3131
{
3232

3333
/**
34-
* @var DOMNamedNodeMap|null
34+
* @var DOMNamedNodeMap<DOMAttr>|null
3535
*/
3636
public $attributes;
3737

3838
/**
39-
* @phpstan-assert-if-true =DOMNamedNodeMap $this->attributes
39+
* @phpstan-assert-if-true =DOMNamedNodeMap<DOMAttr> $this->attributes
4040
* @return bool
4141
*/
4242
public function hasAttributes() {}
@@ -46,7 +46,7 @@ class DOMNode
4646
class DOMElement extends DOMNode
4747
{
4848

49-
/** @var DOMNamedNodeMap */
49+
/** @var DOMNamedNodeMap<DOMAttr> */
5050
public $attributes;
5151

5252
/** @var DOMDocument */
@@ -96,7 +96,7 @@ class DOMXPath
9696

9797
}
9898

99-
class DOMAttr
99+
class DOMAttr extends DOMNode
100100
{
101101

102102
/** @var DOMDocument */
@@ -155,11 +155,37 @@ class DOMProcessingInstruction
155155
}
156156

157157
/**
158+
* @template-covariant TNode as DOMNode
159+
* @implements Traversable<int, TNode>
160+
* @implements IteratorAggregate<int, TNode>
161+
*
158162
* @property-read int $length
159163
*/
160-
class DOMNamedNodeMap
164+
class DOMNamedNodeMap implements Traversable, IteratorAggregate, Countable
161165
{
166+
/**
167+
* @return Iterator<TNode>
168+
*/
169+
public function getIterator(): Iterator {}
170+
171+
/**
172+
* @param string $qualifiedName
173+
* @return TNode|null
174+
*/
175+
public function getNamedItem($qualifiedName): ?DOMNode {}
162176

177+
/**
178+
* @param string|null $namespace
179+
* @param string $localName
180+
* @return TNode|null
181+
*/
182+
public function getNamedItemNS($namespace, $localName): ?DOMNode {}
183+
184+
/**
185+
* @param int $index
186+
* @return TNode|null
187+
*/
188+
public function item($index): ?DOMNode {}
163189
}
164190

165191
class DOMText

tests/PHPStan/Analyser/nsrt/bug-13076.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,18 @@ class Foo
99
public function test(\DOMNode $node): void
1010
{
1111
if ($node->hasAttributes()) {
12-
assertType('DOMNamedNodeMap', $node->attributes);
12+
assertType('DOMNamedNodeMap<DOMAttr>', $node->attributes);
1313
} else {
14-
assertType('DOMNamedNodeMap|null', $node->attributes);
14+
assertType('DOMNamedNodeMap<DOMAttr>|null', $node->attributes);
1515
}
1616
}
1717

1818
public function testElement(\DOMElement $node): void
1919
{
2020
if ($node->hasAttributes()) {
21-
assertType('DOMNamedNodeMap', $node->attributes);
21+
assertType('DOMNamedNodeMap<DOMAttr>', $node->attributes);
2222
} else {
23-
assertType('DOMNamedNodeMap', $node->attributes);
23+
assertType('DOMNamedNodeMap<DOMAttr>', $node->attributes);
2424
}
2525
}
2626
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug13365;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
class Foo
8+
{
9+
public function test(\DOMElement $element): void
10+
{
11+
$attributes = $element->attributes;
12+
13+
if ($attributes === null) {
14+
return;
15+
}
16+
17+
assertType('DOMNamedNodeMap<DOMAttr>', $attributes);
18+
assertType('Iterator<mixed, DOMAttr>', $attributes->getIterator());
19+
assertType('DOMAttr|null', $attributes->getNamedItem('foo'));
20+
assertType('DOMAttr|null', $attributes->getNamedItemNS('foo', 'bar'));
21+
assertType('DOMAttr|null', $attributes->item(0));
22+
23+
foreach ($element->attributes ?? [] as $attr) {
24+
assertType('DOMAttr', $attr);
25+
}
26+
}
27+
}

0 commit comments

Comments
 (0)