Skip to content

Commit 920c6b9

Browse files
authored
Add null to the container get method return type when ContainerInterace::NULL_ON_INVALID_REFERENCE is passed as the second argument. (#610)
Add null to the container get method return type when ContainerInterface::NULL_ON_INVALID_REFERENCE is passed as the second argument.
1 parent 8f57480 commit 920c6b9

File tree

3 files changed

+88
-15
lines changed

3 files changed

+88
-15
lines changed

src/Type/ContainerDynamicReturnTypeExtension.php

Lines changed: 46 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@
99
use PHPStan\Reflection\ParametersAcceptorSelector;
1010
use PHPStan\Type\Constant\ConstantBooleanType;
1111
use PHPStan\Type\DynamicMethodReturnTypeExtension;
12+
use PHPStan\Type\NullType;
1213
use PHPStan\Type\Type;
1314
use PHPStan\Type\TypeCombinator;
14-
use Psr\Container\ContainerInterface;
15+
use Symfony\Component\DependencyInjection\ContainerInterface;
1516

1617
class ContainerDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
1718
{
@@ -41,24 +42,54 @@ public function getTypeFromMethodCall(
4142
Scope $scope
4243
): Type {
4344
$returnType = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType();
44-
$args = $methodCall->getArgs();
45-
if (count($args) !== 1) {
46-
return $returnType;
47-
}
48-
4945
$methodName = $methodReflection->getName();
50-
$types = [];
51-
$argType = $scope->getType($args[0]->value);
5246

53-
foreach ($argType->getConstantStrings() as $constantStringType) {
54-
$serviceId = $constantStringType->getValue();
55-
$service = $this->serviceMap->getService($serviceId);
56-
if ($methodName === 'get') {
57-
$types[] = $service !== null ? $service->getType() : $returnType;
58-
} elseif ($methodName === 'has') {
47+
if ($methodName === 'has') {
48+
$args = $methodCall->getArgs();
49+
if (count($args) !== 1) {
50+
return $returnType;
51+
}
52+
53+
$types = [];
54+
$argType = $scope->getType($args[0]->value);
55+
56+
foreach ($argType->getConstantStrings() as $constantStringType) {
57+
$serviceId = $constantStringType->getValue();
58+
$service = $this->serviceMap->getService($serviceId);
5959
$types[] = new ConstantBooleanType($service !== null);
6060
}
61+
62+
return TypeCombinator::union(...$types);
63+
} elseif ($methodName === 'get') {
64+
$args = $methodCall->getArgs();
65+
if (count($args) === 0) {
66+
return $returnType;
67+
}
68+
69+
$types = [];
70+
71+
if (isset($args[1])) {
72+
$invalidBehaviour = $scope->getType($args[1]->value);
73+
74+
foreach ($invalidBehaviour->getConstantScalarValues() as $value) {
75+
if ($value === ContainerInterface::NULL_ON_INVALID_REFERENCE) {
76+
$types[] = new NullType();
77+
break;
78+
}
79+
}
80+
}
81+
82+
$argType = $scope->getType($args[0]->value);
83+
84+
foreach ($argType->getConstantStrings() as $constantStringType) {
85+
$serviceId = $constantStringType->getValue();
86+
$service = $this->serviceMap->getService($serviceId);
87+
$types[] = $service !== null ? $service->getType() : $returnType;
88+
}
89+
90+
return TypeCombinator::union(...$types);
6191
}
62-
return TypeCombinator::union(...$types);
92+
93+
return $returnType;
6394
}
6495
}

tests/src/Type/DrupalContainerDynamicReturnTypeTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ public function dataFileAsserts(): iterable
1717
yield from self::gatherAssertTypes(__DIR__ . '/data/drupal-service-static.php');
1818
yield from self::gatherAssertTypes(__DIR__ . '/data/drupal-class-resolver.php');
1919
yield from self::gatherAssertTypes(__DIR__ . '/data/bug-563.php');
20+
yield from self::gatherAssertTypes(__DIR__ . '/data/container-optional.php');
2021
}
2122

2223
/**
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
namespace DrupalContainerOptional;
4+
5+
use Symfony\Component\DependencyInjection\ContainerInterface;
6+
use Drupal\service_map\MyService;
7+
use function PHPStan\Testing\assertType;
8+
9+
function test(): void {
10+
$container = \Drupal::getContainer();
11+
12+
assertType(
13+
'object|null',
14+
$container->get('unknown_service', ContainerInterface::NULL_ON_INVALID_REFERENCE)
15+
);
16+
17+
assertType(
18+
MyService::class . '|null',
19+
$container->get('service_map.my_service', ContainerInterface::NULL_ON_INVALID_REFERENCE)
20+
);
21+
22+
assertType(
23+
MyService::class,
24+
$container->get('service_map.my_service', ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE)
25+
);
26+
27+
assertType(
28+
MyService::class,
29+
$container->get('service_map.my_service', ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE)
30+
);
31+
32+
assertType(
33+
MyService::class,
34+
$container->get('service_map.my_service', ContainerInterface::IGNORE_ON_INVALID_REFERENCE)
35+
);
36+
37+
assertType(
38+
MyService::class,
39+
$container->get('service_map.my_service', ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE)
40+
);
41+
}

0 commit comments

Comments
 (0)