Skip to content

Commit 9242eb0

Browse files
committed
Support entity query execute dynamic return type whether count() method has been called or not
1 parent 958911d commit 9242eb0

File tree

4 files changed

+124
-0
lines changed

4 files changed

+124
-0
lines changed

extension.neon

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ services:
4343
-
4444
class: mglaman\PHPStanDrupal\Type\DrupalClassResolverDynamicReturnTypeExtension
4545
tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
46+
-
47+
class: mglaman\PHPStanDrupal\Type\EntityQueryDynamicReturnTypeExtension
48+
tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
4649
-
4750
class: mglaman\PHPStanDrupal\Type\DrupalClassResolverDynamicStaticReturnTypeExtension
4851
tags: [phpstan.broker.dynamicStaticMethodReturnTypeExtension]
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace mglaman\PHPStanDrupal\Type;
4+
5+
use Drupal\Core\Entity\Query\QueryInterface;
6+
use PhpParser\Node\Expr\MethodCall;
7+
use PhpParser\Node\Identifier;
8+
use PHPStan\Analyser\Scope;
9+
use PHPStan\Reflection\MethodReflection;
10+
use PHPStan\Reflection\ParametersAcceptorSelector;
11+
use PHPStan\Type\ArrayType;
12+
use PHPStan\Type\DynamicMethodReturnTypeExtension;
13+
use PHPStan\Type\IntegerType;
14+
use PHPStan\Type\ObjectType;
15+
use PHPStan\Type\StringType;
16+
use PHPStan\Type\Type;
17+
18+
class EntityQueryDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
19+
{
20+
21+
public function getClass(): string
22+
{
23+
return QueryInterface::class;
24+
}
25+
26+
public function isMethodSupported(MethodReflection $methodReflection): bool
27+
{
28+
return $methodReflection->getName() === 'execute';
29+
}
30+
31+
public function getTypeFromMethodCall(
32+
MethodReflection $methodReflection,
33+
MethodCall $methodCall,
34+
Scope $scope
35+
): Type {
36+
$defaultReturnType = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType();
37+
38+
$queryBuilderType = new ObjectType(QueryInterface::class);
39+
$var = $methodCall->var;
40+
while ($var instanceof MethodCall) {
41+
$varType = $scope->getType($var->var);
42+
if (!$queryBuilderType->isSuperTypeOf($varType)->yes()) {
43+
return new ArrayType(new IntegerType(), new StringType());
44+
}
45+
46+
$nameObject = $var->name;
47+
if (!($nameObject instanceof Identifier)) {
48+
return $defaultReturnType;
49+
}
50+
51+
$name = $nameObject->toString();
52+
if ($name === 'count') {
53+
return new IntegerType();
54+
}
55+
56+
$var = $var->var;
57+
}
58+
59+
return $defaultReturnType;
60+
}
61+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace mglaman\PHPStanDrupal\Tests\Type;
6+
7+
use mglaman\PHPStanDrupal\Tests\AdditionalConfigFilesTrait;
8+
use PHPStan\Testing\TypeInferenceTestCase;
9+
10+
final class EntityQueryDynamicReturnTypeExtensionTest extends TypeInferenceTestCase
11+
{
12+
use AdditionalConfigFilesTrait;
13+
14+
public function dataFileAsserts(): iterable
15+
{
16+
yield from $this->gatherAssertTypes(__DIR__ . '/data/entity-query-execute.php');
17+
}
18+
19+
/**
20+
* @dataProvider dataFileAsserts
21+
* @param string $assertType
22+
* @param string $file
23+
* @param mixed ...$args
24+
*/
25+
public function testFileAsserts(
26+
string $assertType,
27+
string $file,
28+
...$args
29+
): void
30+
{
31+
$this->assertFileAsserts($assertType, $file, ...$args);
32+
}
33+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
namespace DrupalEntity;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
assertType(
8+
'array<int, string>',
9+
\Drupal::entityTypeManager()->getStorage('node')->getQuery()
10+
->accessCheck(TRUE)
11+
->execute()
12+
);
13+
assertType(
14+
'int',
15+
\Drupal::entityTypeManager()->getStorage('node')->getQuery()
16+
->accessCheck(TRUE)
17+
->count()
18+
->execute()
19+
);
20+
21+
// @todo: find a way support these cases.
22+
//$query = \Drupal::entityTypeManager()->getStorage('node')->getQuery()
23+
// ->accessCheck(TRUE);
24+
//assertType('array<int, string>', $query->execute());
25+
//$query = \Drupal::entityTypeManager()->getStorage('node')->getQuery()
26+
// ->accessCheck(TRUE)->count();
27+
//assertType('int', $query->execute());

0 commit comments

Comments
 (0)