Skip to content

Commit f560547

Browse files
committed
Adding ability to cast iterable classes into array of objects
1 parent f8465ac commit f560547

File tree

3 files changed

+54
-3
lines changed

3 files changed

+54
-3
lines changed

src/Rules/TypeHints/AbstractMissingTypeHintRule.php

+26-3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use phpDocumentor\Reflection\Types\Callable_;
1111
use phpDocumentor\Reflection\Types\Float_;
1212
use phpDocumentor\Reflection\Types\Integer;
13+
use phpDocumentor\Reflection\Types\Iterable_;
1314
use phpDocumentor\Reflection\Types\Mixed_;
1415
use phpDocumentor\Reflection\Types\Null_;
1516
use phpDocumentor\Reflection\Types\Object_;
@@ -24,6 +25,7 @@
2425
use Roave\BetterReflection\Reflection\ReflectionFunction;
2526
use Roave\BetterReflection\Reflection\ReflectionMethod;
2627
use Roave\BetterReflection\Reflection\ReflectionParameter;
28+
use Roave\BetterReflection\Reflector\Exception\IdentifierNotFound;
2729
use Roave\BetterReflection\TypesFinder\PhpDocumentor\NamespaceNodeToReflectionTypeContext;
2830
use Roave\BetterReflection\TypesFinder\ResolveTypes;
2931

@@ -162,7 +164,7 @@ private function analyzeWithTypehint($context, Type $phpTypeHint, array $docBloc
162164
{
163165
$docblockWithoutNullable = $this->typesWithoutNullable($docBlockTypeHints);
164166

165-
if (!$phpTypeHint instanceof Array_) {
167+
if (!$this->isTypeIterable($phpTypeHint)) {
166168
// Let's detect mismatches between docblock and PHP typehint
167169
foreach ($docblockWithoutNullable as $docblockTypehint) {
168170
if (get_class($docblockTypehint) !== get_class($phpTypeHint)) {
@@ -185,15 +187,15 @@ private function analyzeWithTypehint($context, Type $phpTypeHint, array $docBloc
185187
}
186188
} else {
187189
foreach ($docblockWithoutNullable as $docblockTypehint) {
188-
if (!$docblockTypehint instanceof Array_) {
190+
if (!$this->isTypeIterable($docblockTypehint)) {
189191
if ($context instanceof ReflectionParameter) {
190192
return sprintf('%s, mismatching type-hints for parameter %s. PHP type hint is "array" and docblock type hint is %s.', $this->getContext($context), $context->getName(), (string)$docblockTypehint);
191193
} else {
192194
return sprintf('%s, mismatching type-hints for return type. PHP type hint is "array" and docblock declared return type is %s.', $this->getContext($context), (string)$docblockTypehint);
193195
}
194196
}
195197

196-
if ($docblockTypehint->getValueType() instanceof Mixed_) {
198+
if ($docblockTypehint instanceof Array_ && $docblockTypehint->getValueType() instanceof Mixed_) {
197199
if (!$this->findExplicitMixedArray($context)) {
198200
if ($context instanceof ReflectionParameter) {
199201
return sprintf('%s, parameter $%s type is "array". Please provide a more specific @param annotation in the docblock. For instance: @param int[] $%s. Use @param mixed[] $%s if this is really an array of mixed values.', $this->getContext($context), $context->getName(), $context->getName(), $context->getName());
@@ -208,6 +210,27 @@ private function analyzeWithTypehint($context, Type $phpTypeHint, array $docBloc
208210
return null;
209211
}
210212

213+
private function isTypeIterable(Type $phpTypeHint) : bool
214+
{
215+
if ($phpTypeHint instanceof Array_ || $phpTypeHint instanceof Iterable_) {
216+
return true;
217+
}
218+
if ($phpTypeHint instanceof Object_) {
219+
// TODO: cache BetterReflection for better performance!
220+
try {
221+
$class = (new BetterReflection())->classReflector()->reflect((string) $phpTypeHint);
222+
} catch (IdentifierNotFound $e) {
223+
// Class not found? Let's not throw an error. It will be caught by other rules anyway.
224+
return false;
225+
}
226+
if ($class->implementsInterface('Traversable')) {
227+
return true;
228+
}
229+
}
230+
231+
return false;
232+
}
233+
211234
/**
212235
* @param ReflectionParameter|ReflectionMethod|ReflectionFunction $context
213236
* @return bool
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
4+
namespace TheCodingMachine\PHPStan\Rules\TypeHints\data;
5+
6+
7+
use ArrayIterator;
8+
9+
class StubIterator implements \IteratorAggregate
10+
{
11+
public function getIterator() {
12+
return new ArrayIterator($this);
13+
}
14+
}

tests/Rules/TypeHints/data/typehints.php

+14
Original file line numberDiff line numberDiff line change
@@ -76,3 +76,17 @@ function test9(DateTimeInterface ...$dates): void
7676
function test10($id): void
7777
{
7878
}
79+
80+
/**
81+
* @param DateTimeInterface[] $type_hintable
82+
*/
83+
function test11(iterable $type_hintable): void
84+
{
85+
}
86+
87+
/**
88+
* @param TheCodingMachine\PHPStan\Rules\TypeHints\data\StubIterator|DateTimeInterface[] $type_hintable
89+
*/
90+
function test12(TheCodingMachine\PHPStan\Rules\TypeHints\data\StubIterator $type_hintable): void
91+
{
92+
}

0 commit comments

Comments
 (0)