Skip to content

Commit 6f96a72

Browse files
authored
Merge pull request #355 from brambaud/typed-entity-storage-does-not-succeed-to-infer-query-type
Entity storage with type hint prevent inferring entity query execute return type
2 parents a8ec61b + d8ae186 commit 6f96a72

File tree

6 files changed

+139
-12
lines changed

6 files changed

+139
-12
lines changed

src/Drupal/EntityDataRepository.php

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,33 +2,44 @@
22

33
namespace mglaman\PHPStanDrupal\Drupal;
44

5-
use PHPStan\Reflection\ReflectionProvider;
5+
use PHPStan\Type\ObjectType;
66

77
final class EntityDataRepository
88
{
9-
10-
/**
11-
* @var array<string, array<string, string>>
12-
*/
13-
private $entityMapping;
149
/**
15-
* @var array<string, EntityData|null>
10+
* @var array<string, EntityData>
1611
*/
1712
private $entityData;
1813

1914
public function __construct(array $entityMapping)
2015
{
21-
$this->entityMapping = $entityMapping;
16+
foreach ($entityMapping as $entityTypeId => $entityData) {
17+
$this->entityData[$entityTypeId] = new EntityData(
18+
$entityTypeId,
19+
$entityData
20+
);
21+
}
2222
}
2323

2424
public function get(string $entityTypeId): EntityData
2525
{
2626
if (!isset($this->entityData[$entityTypeId])) {
2727
$this->entityData[$entityTypeId] = new EntityData(
2828
$entityTypeId,
29-
$this->entityMapping[$entityTypeId] ?? []
29+
[]
3030
);
3131
}
3232
return $this->entityData[$entityTypeId];
3333
}
34+
35+
public function resolveFromStorage(ObjectType $callerType): ?EntityData
36+
{
37+
foreach ($this->entityData as $entityData) {
38+
$storageType = $entityData->getStorageType();
39+
if ($storageType !== null && $callerType->isSuperTypeOf($storageType)->yes()) {
40+
return $entityData;
41+
}
42+
}
43+
return null;
44+
}
3445
}

src/Type/EntityStorage/EntityStorageDynamicReturnTypeExtension.php

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use PHPStan\Type\ArrayType;
1212
use PHPStan\Type\DynamicMethodReturnTypeExtension;
1313
use PHPStan\Type\IntegerType;
14+
use PHPStan\Type\ObjectType;
1415
use PHPStan\Type\StringType;
1516
use PHPStan\Type\TypeCombinator;
1617

@@ -53,12 +54,20 @@ public function getTypeFromMethodCall(
5354
Scope $scope
5455
): \PHPStan\Type\Type {
5556
$callerType = $scope->getType($methodCall->var);
57+
if (!$callerType instanceof ObjectType) {
58+
return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType();
59+
}
5660

5761
if (!$callerType instanceof EntityStorageType) {
58-
return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType();
62+
$resolvedEntityType = $this->entityDataRepository->resolveFromStorage($callerType);
63+
if ($resolvedEntityType === null) {
64+
return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType();
65+
}
66+
$type = $resolvedEntityType->getClassType();
67+
} else {
68+
$type = $this->entityDataRepository->get($callerType->getEntityTypeId())->getClassType();
5969
}
6070

61-
$type = $this->entityDataRepository->get($callerType->getEntityTypeId())->getClassType();
6271
if ($type === null) {
6372
return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType();
6473
}
@@ -74,6 +83,10 @@ public function getTypeFromMethodCall(
7483
return new ArrayType(new IntegerType(), $type);
7584
}
7685

77-
return $type;
86+
if ($methodReflection->getName() === 'create') {
87+
return $type;
88+
}
89+
90+
return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType();
7891
}
7992
}

tests/src/Type/EntityDynamicReturnTypeTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ public function dataFileAsserts(): iterable
1515
{
1616
yield from $this->gatherAssertTypes(__DIR__ . '/data/entity.php');
1717
yield from $this->gatherAssertTypes(__DIR__ . '/data/entity-storage.php');
18+
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-355-entity-storage.php');
1819
}
1920

2021
/**

tests/src/Type/EntityQuery/EntityQueryDynamicReturnTypeExtensionTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ final class EntityQueryDynamicReturnTypeExtensionTest extends TypeInferenceTestC
1414
public function dataFileAsserts(): iterable
1515
{
1616
yield from $this->gatherAssertTypes(__DIR__ . '/../data/entity-query-execute.php');
17+
yield from $this->gatherAssertTypes(__DIR__ . '/../data/bug-355-entity-query.php');
1718
}
1819

1920
/**
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
<?php
2+
3+
namespace DrupalEntity;
4+
5+
use Drupal\node\NodeStorage;
6+
use function PHPStan\Testing\assertType;
7+
8+
/** @var \Drupal\node\NodeStorage $typedNodeStorage */
9+
$typedNodeStorage = \Drupal::entityTypeManager()->getStorage('node');
10+
assertType(
11+
'array<int, string>',
12+
$typedNodeStorage->getQuery()
13+
->accessCheck(TRUE)
14+
->execute()
15+
);
16+
$query = $typedNodeStorage->getQuery()
17+
->accessCheck(TRUE);
18+
assertType('array<int, string>', $query->execute());
19+
assertType(
20+
'int',
21+
$typedNodeStorage->getQuery()
22+
->accessCheck(TRUE)
23+
->count()
24+
->execute()
25+
);
26+
$query = $typedNodeStorage->getQuery()
27+
->accessCheck(TRUE)
28+
->count();
29+
assertType('int', $query->execute());
30+
31+
/** @var \Drupal\node\NodeStorageInterface $anotherTypedNodeStorage */
32+
$anotherTypedNodeStorage = \Drupal::entityTypeManager()->getStorage('node');
33+
assertType(
34+
'array<int, string>',
35+
$anotherTypedNodeStorage->getQuery()
36+
->accessCheck(TRUE)
37+
->execute()
38+
);
39+
$query = $anotherTypedNodeStorage->getQuery()
40+
->accessCheck(TRUE);
41+
assertType('array<int, string>', $query->execute());
42+
assertType(
43+
'int',
44+
$anotherTypedNodeStorage->getQuery()
45+
->accessCheck(TRUE)
46+
->count()
47+
->execute()
48+
);
49+
$query = $anotherTypedNodeStorage->getQuery()
50+
->accessCheck(TRUE)
51+
->count();
52+
assertType('int', $query->execute());
53+
54+
$instanceOfNodeStorage = \Drupal::entityTypeManager()->getStorage('node');
55+
if ($instanceOfNodeStorage instanceof NodeStorage) {
56+
assertType(
57+
'array<int, string>',
58+
$instanceOfNodeStorage->getQuery()
59+
->accessCheck(TRUE)
60+
->execute()
61+
);
62+
$query = $instanceOfNodeStorage->getQuery()
63+
->accessCheck(TRUE);
64+
assertType('array<int, string>', $query->execute());
65+
assertType(
66+
'int',
67+
$instanceOfNodeStorage->getQuery()
68+
->accessCheck(TRUE)
69+
->count()
70+
->execute()
71+
);
72+
$query = $instanceOfNodeStorage->getQuery()
73+
->accessCheck(TRUE)
74+
->count();
75+
assertType('int', $query->execute());
76+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
namespace DrupalEntity;
4+
5+
use Drupal\node\NodeStorageInterface;
6+
use function PHPStan\Testing\assertType;
7+
8+
$nodeStorage = \Drupal::entityTypeManager()->getStorage('node');
9+
if ($nodeStorage instanceof NodeStorageInterface) {
10+
assertType('Drupal\node\Entity\Node', $nodeStorage->create(['type' => 'page', 'title' => 'foo']));
11+
assertType('Drupal\node\Entity\Node|null', $nodeStorage->load(42));
12+
assertType('array<int, Drupal\node\Entity\Node>', $nodeStorage->loadMultiple([42, 29]));
13+
}
14+
15+
/** @var \Drupal\node\NodeStorage $typedNodeStorage */
16+
$typedNodeStorage = \Drupal::entityTypeManager()->getStorage('node');
17+
assertType('Drupal\node\Entity\Node', $typedNodeStorage->create(['type' => 'page', 'title' => 'foo']));
18+
assertType('Drupal\node\Entity\Node|null', $typedNodeStorage->load(42));
19+
assertType('array<int, Drupal\node\Entity\Node>', $typedNodeStorage->loadMultiple([42, 29]));
20+
21+
/** @var \Drupal\taxonomy\TermStorageInterface $termStorage */
22+
$termStorage = \Drupal::entityTypeManager()->getStorage('taxonomy_term');
23+
assertType('Drupal\taxonomy\Entity\Term', $termStorage->create(['vid' => 'tag', 'name' => 'foo']));
24+
assertType('Drupal\taxonomy\Entity\Term|null', $termStorage->load('42'));
25+
assertType('array<int, Drupal\taxonomy\Entity\Term>', $termStorage->loadMultiple([42, 29]));

0 commit comments

Comments
 (0)