Skip to content

Commit b615d40

Browse files
dpimglaman
andauthored
Narrow return value of EntityInterface->id() by previous calls to isNew on an instance of EntityInterface (#900)
* Narrow return value of `EntityInterface->id()` by previous calls to `isNew` on an instance of `EntityInterface` * simplify/formatting/ address phpstan deprecation about false * phpcs --------- Co-authored-by: Matt Glaman <[email protected]>
1 parent 5ec8cb9 commit b615d40

File tree

4 files changed

+65
-0
lines changed

4 files changed

+65
-0
lines changed

extension.neon

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,9 @@ services:
287287
class: mglaman\PHPStanDrupal\Drupal\EntityDataRepository
288288
arguments:
289289
entityMapping: %drupal.entityMapping%
290+
-
291+
class: mglaman\PHPStanDrupal\Type\EntityIdNarrowedByNew
292+
tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
290293
-
291294
class: mglaman\PHPStanDrupal\Type\EntityTypeManagerGetStorageDynamicReturnTypeExtension
292295
tags: [phpstan.broker.dynamicMethodReturnTypeExtension]

src/Type/EntityIdNarrowedByNew.php

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace mglaman\PHPStanDrupal\Type;
4+
5+
use Drupal\Core\Entity\EntityInterface;
6+
use PhpParser\Node\Expr\MethodCall;
7+
use PhpParser\Node\Identifier;
8+
use PHPStan\Analyser\Scope;
9+
use PHPStan\Reflection\MethodReflection;
10+
use PHPStan\Type\DynamicMethodReturnTypeExtension;
11+
use PHPStan\Type\IntegerType;
12+
use PHPStan\Type\NullType;
13+
use PHPStan\Type\StringType;
14+
use PHPStan\Type\Type;
15+
use PHPStan\Type\TypeCombinator;
16+
17+
/**
18+
* @author Daniel Phin <[email protected]>
19+
*/
20+
class EntityIdNarrowedByNew implements DynamicMethodReturnTypeExtension
21+
{
22+
23+
public function getClass(): string
24+
{
25+
return EntityInterface::class;
26+
}
27+
28+
public function isMethodSupported(MethodReflection $methodReflection): bool
29+
{
30+
return $methodReflection->getName() === 'id';
31+
}
32+
33+
public function getTypeFromMethodCall(
34+
MethodReflection $methodReflection,
35+
MethodCall $methodCall,
36+
Scope $scope
37+
): ?Type {
38+
$isNewMethodCall = new MethodCall($methodCall->var, new Identifier('isNew'));
39+
if ($scope->getType($isNewMethodCall)->isFalse()->yes()) {
40+
return TypeCombinator::union(new IntegerType(), new StringType());
41+
}
42+
43+
return TypeCombinator::union(
44+
new IntegerType(),
45+
new StringType(),
46+
new NullType()
47+
);
48+
}
49+
}

stubs/Drupal/Core/Entity/EntityInterface.stub

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,7 @@ namespace Drupal\Core\Entity;
44

55
interface EntityInterface {
66

7+
public function id(): string|int|null;
8+
public function isNew(): bool;
9+
710
}

tests/src/Type/data/entity.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace DrupalEntity;
44

55
use Drupal\node\Entity\Node;
6+
use Drupal\node\NodeInterface;
67
use function PHPStan\Testing\assertType;
78

89
assertType('Drupal\node\Entity\Node', $node = Node::create(['type' => 'page', 'title' => 'foo']));
@@ -18,3 +19,12 @@
1819
assertType('array<Drupal\node\Entity\Node>', Node::loadMultiple([42, 29]));
1920
assertType('array<Drupal\node\Entity\Node>', Node::loadMultiple(['42', '29']));
2021
assertType('array<Drupal\node\Entity\Node>', Node::loadMultiple(NULL));
22+
23+
assert($id1 instanceof NodeInterface);
24+
assertType('int|string|null', $id1->id());
25+
assert($id2 instanceof NodeInterface);
26+
assert($id2->isNew() === TRUE);
27+
assertType('int|string|null', $id2->id());
28+
assert($id3 instanceof NodeInterface);
29+
assert($id3->isNew() === FALSE);
30+
assertType('int|string', $id3->id());

0 commit comments

Comments
 (0)