Skip to content

Commit d043a25

Browse files
dpimglaman
andauthored
✨ Narrow return value of getLinkTemplate by whether hasLinkTemplate was called beforehand (#906)
✨ Narrow return value of getLinkTemplate by whether hastLinkTemplate was called beforehand Co-authored-by: Matt Glaman <[email protected]>
1 parent 84424c1 commit d043a25

File tree

4 files changed

+80
-0
lines changed

4 files changed

+80
-0
lines changed

extension.neon

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,9 @@ services:
290290
-
291291
class: mglaman\PHPStanDrupal\Type\EntityIdNarrowedByNew
292292
tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
293+
-
294+
class: mglaman\PHPStanDrupal\Type\EntityTypeLinkTemplateByExistence
295+
tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
293296
-
294297
class: mglaman\PHPStanDrupal\Type\EntityTypeManagerGetStorageDynamicReturnTypeExtension
295298
tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace mglaman\PHPStanDrupal\Type;
4+
5+
use Drupal\Core\Entity\EntityTypeInterface;
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\Constant\ConstantBooleanType;
11+
use PHPStan\Type\DynamicMethodReturnTypeExtension;
12+
use PHPStan\Type\StringType;
13+
use PHPStan\Type\Type;
14+
15+
/**
16+
* @author Daniel Phin <[email protected]>
17+
*/
18+
class EntityTypeLinkTemplateByExistence implements DynamicMethodReturnTypeExtension
19+
{
20+
21+
public function getClass(): string
22+
{
23+
return EntityTypeInterface::class;
24+
}
25+
26+
public function isMethodSupported(MethodReflection $methodReflection): bool
27+
{
28+
return $methodReflection->getName() === 'getLinkTemplate';
29+
}
30+
31+
public function getTypeFromMethodCall(
32+
MethodReflection $methodReflection,
33+
MethodCall $methodCall,
34+
Scope $scope
35+
): ?Type {
36+
if (null === ($keyArg = $methodCall->getArg('key', 0))) {
37+
return null;
38+
}
39+
40+
$originalReturnType = $methodReflection->getVariants()[0]->getReturnType();
41+
$hasKeyMethodCall = new MethodCall($methodCall->var, new Identifier('hasLinkTemplate'), [$keyArg]);
42+
if ($scope->getType($hasKeyMethodCall)->isTrue()->yes()) {
43+
return $originalReturnType->tryRemove(new ConstantBooleanType(false));
44+
} elseif ($scope->getType($hasKeyMethodCall)->isFalse()->yes()) {
45+
return $originalReturnType->tryRemove(new StringType());
46+
}
47+
48+
return $originalReturnType;
49+
}
50+
}

stubs/Drupal/Core/Entity/EntityTypeInterface.stub

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

55
interface EntityTypeInterface {
66

7+
/**
8+
* @return string|false
9+
*/
10+
public function getLinkTemplate(string $key);
11+
712
}

tests/src/Type/data/entity-type-stubs.php

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

55
use Drupal\Core\Entity\ContentEntityInterface;
6+
use Drupal\Core\Entity\EntityType;
67
use Drupal\Core\Field\FieldItemListInterface;
78
use Drupal\node\Entity\Node;
89

@@ -15,3 +16,24 @@ function (ContentEntityInterface $entity): void
1516
assertType(FieldItemListInterface::class, $field);
1617
}
1718
};
19+
20+
assert($entityTypeDefault instanceof EntityType);
21+
assertType('string|false', $entityTypeDefault->getLinkTemplate('foo'));
22+
23+
assert($noLinkTemplate instanceof EntityType);
24+
assert($noLinkTemplate->hasLinkTemplate('foo') === FALSE);
25+
assertType('false', $noLinkTemplate->getLinkTemplate('foo'));
26+
27+
assert($hasLinkTemplate instanceof EntityType);
28+
assert($hasLinkTemplate->hasLinkTemplate('foo') === TRUE);
29+
assertType('string', $hasLinkTemplate->getLinkTemplate('foo'));
30+
31+
// Test getting a link template doesn't affect another link template.
32+
assert($entityType instanceof EntityType);
33+
assert($entityType->hasLinkTemplate('foo') === TRUE);
34+
assertType('string', $entityType->getLinkTemplate('foo'));
35+
// A different arg that wasn't narrowed previously:
36+
assertType('string|false', $entityType->getLinkTemplate('bar'));
37+
// ...until we know better:
38+
assert($entityType->hasLinkTemplate('bar') === TRUE);
39+
assertType('string', $entityType->getLinkTemplate('bar'));

0 commit comments

Comments
 (0)