Skip to content

Commit 66218a0

Browse files
authored
Merge pull request #241 from brambaud/feat/support-entity-query-execute
Support entity query execute dynamic return type whether it is a count query or not
2 parents c9fafc5 + cb663a3 commit 66218a0

File tree

5 files changed

+158
-0
lines changed

5 files changed

+158
-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\EntityQuery\EntityQueryDynamicReturnTypeExtension
48+
tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
4649
-
4750
class: mglaman\PHPStanDrupal\Type\DrupalClassResolverDynamicStaticReturnTypeExtension
4851
tags: [phpstan.broker.dynamicStaticMethodReturnTypeExtension]
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace mglaman\PHPStanDrupal\Type\EntityQuery;
4+
5+
use PHPStan\Type\ObjectType;
6+
7+
/**
8+
* Type used to represent an entity query instance as count query.
9+
*/
10+
final class EntityQueryCountType extends ObjectType
11+
{
12+
13+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace mglaman\PHPStanDrupal\Type\EntityQuery;
4+
5+
use Drupal\Core\Entity\Query\QueryInterface;
6+
use PhpParser\Node\Expr\MethodCall;
7+
use PHPStan\Analyser\Scope;
8+
use PHPStan\Reflection\MethodReflection;
9+
use PHPStan\Reflection\ParametersAcceptorSelector;
10+
use PHPStan\Type\ArrayType;
11+
use PHPStan\Type\DynamicMethodReturnTypeExtension;
12+
use PHPStan\Type\IntegerType;
13+
use PHPStan\Type\ObjectType;
14+
use PHPStan\Type\StringType;
15+
use PHPStan\Type\Type;
16+
17+
class EntityQueryDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
18+
{
19+
20+
public function getClass(): string
21+
{
22+
return QueryInterface::class;
23+
}
24+
25+
public function isMethodSupported(MethodReflection $methodReflection): bool
26+
{
27+
return in_array($methodReflection->getName(), [
28+
'count',
29+
'execute',
30+
], true);
31+
}
32+
33+
public function getTypeFromMethodCall(
34+
MethodReflection $methodReflection,
35+
MethodCall $methodCall,
36+
Scope $scope
37+
): Type {
38+
$defaultReturnType = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType();
39+
$varType = $scope->getType($methodCall->var);
40+
$methodName = $methodReflection->getName();
41+
if ($methodName === 'count') {
42+
if ($varType instanceof ObjectType) {
43+
return new EntityQueryCountType(
44+
$varType->getClassName(),
45+
$varType->getSubtractedType(),
46+
$varType->getClassReflection()
47+
);
48+
}
49+
return $defaultReturnType;
50+
}
51+
52+
if ($methodName === 'execute') {
53+
if ($varType instanceof EntityQueryCountType) {
54+
return new IntegerType();
55+
}
56+
if ($varType instanceof ObjectType) {
57+
// @todo if this is a config storage, it'd string keys.
58+
// revisit after https://github.com/mglaman/phpstan-drupal/pull/239
59+
// then we can check what kind of storage we have.
60+
return new ArrayType(new IntegerType(), new StringType());
61+
}
62+
return $defaultReturnType;
63+
}
64+
return $defaultReturnType;
65+
}
66+
}
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\EntityQuery;
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: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
namespace DrupalEntity;
4+
5+
use function PHPStan\Testing\assertType;
6+
assertType(
7+
'array<int, string>',
8+
\Drupal::entityTypeManager()->getStorage('node')->getQuery()
9+
->execute()
10+
);
11+
assertType(
12+
'array<int, string>',
13+
\Drupal::entityTypeManager()->getStorage('node')->getQuery()
14+
->accessCheck(TRUE)
15+
->execute()
16+
);
17+
assertType(
18+
'int',
19+
\Drupal::entityTypeManager()->getStorage('node')->getQuery()
20+
->accessCheck(TRUE)
21+
->count()
22+
->execute()
23+
);
24+
assertType(
25+
'int',
26+
\Drupal::entityQuery('node')
27+
->accessCheck(TRUE)
28+
->count()
29+
->execute()
30+
);
31+
assertType(
32+
'array<int, string>',
33+
\Drupal::entityQuery('node')
34+
->accessCheck(TRUE)
35+
->execute()
36+
);
37+
38+
$query = \Drupal::entityTypeManager()->getStorage('node')->getQuery()
39+
->accessCheck(TRUE);
40+
assertType('array<int, string>', $query->execute());
41+
$query = \Drupal::entityTypeManager()->getStorage('node')->getQuery()
42+
->accessCheck(TRUE)->count();
43+
assertType('int', $query->execute());

0 commit comments

Comments
 (0)