Skip to content

Commit f3fa53f

Browse files
Support container interfaces for get() return type (#39)
Co-authored-by: Jens Hatlak <[email protected]>
1 parent 2aa4998 commit f3fa53f

5 files changed

+142
-95
lines changed

extension.neon

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,14 @@ services:
1717
class: LaminasPhpStan\ServiceManagerLoader
1818
arguments:
1919
serviceManagerLoader: %laminasframework.serviceManagerLoader%
20+
-
21+
class: LaminasPhpStan\Type\Laminas\InteropContainerGetDynamicReturnTypeExtension
22+
tags:
23+
- phpstan.broker.dynamicMethodReturnTypeExtension
24+
-
25+
class: LaminasPhpStan\Type\Laminas\PsrContainerGetDynamicReturnTypeExtension
26+
tags:
27+
- phpstan.broker.dynamicMethodReturnTypeExtension
2028
-
2129
class: LaminasPhpStan\Type\Laminas\ServiceManagerGetDynamicReturnTypeExtension
2230
tags:
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace LaminasPhpStan\Type\Laminas;
6+
7+
use Laminas\ServiceManager\AbstractPluginManager;
8+
use LaminasPhpStan\ServiceManagerLoader;
9+
use PhpParser\Node\Arg;
10+
use PhpParser\Node\Expr\MethodCall;
11+
use PHPStan\Analyser\Scope;
12+
use PHPStan\Reflection\MethodReflection;
13+
use PHPStan\Reflection\ReflectionProvider;
14+
use PHPStan\Type\Constant\ConstantStringType;
15+
use PHPStan\Type\DynamicMethodReturnTypeExtension;
16+
use PHPStan\Type\MixedType;
17+
use PHPStan\Type\NeverType;
18+
use PHPStan\Type\ObjectType;
19+
use PHPStan\Type\Type;
20+
use ReflectionClass;
21+
22+
abstract class AbstractServiceLocatorGetDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
23+
{
24+
public function __construct(
25+
private ReflectionProvider $reflectionProvider,
26+
private ServiceManagerLoader $serviceManagerLoader
27+
) {
28+
}
29+
30+
final public function isMethodSupported(MethodReflection $methodReflection): bool
31+
{
32+
return 'get' === $methodReflection->getName();
33+
}
34+
35+
final public function getTypeFromMethodCall(
36+
MethodReflection $methodReflection,
37+
MethodCall $methodCall,
38+
Scope $scope
39+
): Type {
40+
$calledOnType = $scope->getType($methodCall->var);
41+
if (! $calledOnType instanceof ObjectType) {
42+
return new MixedType();
43+
}
44+
45+
$args = $methodCall->getArgs();
46+
if (1 !== \count($args)) {
47+
return new MixedType();
48+
}
49+
50+
$serviceManager = $this->serviceManagerLoader->getServiceLocator($calledOnType->getClassName());
51+
52+
$firstArg = $args[0];
53+
if (! $firstArg instanceof Arg) {
54+
throw new \PHPStan\ShouldNotHappenException(\sprintf(
55+
'Argument passed to %s::%s should be a string, %s given',
56+
$methodReflection->getDeclaringClass()->getName(),
57+
$methodReflection->getName(),
58+
$firstArg->getType()
59+
));
60+
}
61+
$argType = $scope->getType($firstArg->value);
62+
if (! $argType instanceof ConstantStringType) {
63+
if ($serviceManager instanceof AbstractPluginManager) {
64+
$refClass = new ReflectionClass($serviceManager);
65+
$refProperty = $refClass->getProperty('instanceOf');
66+
$refProperty->setAccessible(true);
67+
$returnedInstance = $refProperty->getValue($serviceManager);
68+
\assert(null === $returnedInstance || \is_string($returnedInstance));
69+
if (null !== $returnedInstance && $this->reflectionProvider->hasClass($returnedInstance)) {
70+
return new ObjectType($returnedInstance);
71+
}
72+
}
73+
74+
return new MixedType();
75+
}
76+
77+
$serviceName = $argType->getValue();
78+
if (! $serviceManager->has($serviceName)) {
79+
return new NeverType();
80+
}
81+
82+
if (\class_exists($serviceName) || \interface_exists($serviceName)) {
83+
return new ObjectServiceManagerType($serviceName, $serviceName);
84+
}
85+
86+
$service = $serviceManager->get($serviceName);
87+
if (\is_object($service)) {
88+
$className = $service::class;
89+
$refClass = new ReflectionClass($service);
90+
if ($refClass->isAnonymous()) {
91+
if (false !== ($parentClass = $refClass->getParentClass())) {
92+
$className = $parentClass->getName();
93+
} elseif ([] !== ($interfaces = $refClass->getInterfaces())) {
94+
$className = \current($interfaces)->getName();
95+
}
96+
}
97+
98+
return new ObjectServiceManagerType($className, $serviceName);
99+
}
100+
101+
return $scope->getTypeFromValue($service);
102+
}
103+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace LaminasPhpStan\Type\Laminas;
6+
7+
use Interop\Container\Containerinterface;
8+
9+
final class InteropContainerGetDynamicReturnTypeExtension extends AbstractServiceLocatorGetDynamicReturnTypeExtension
10+
{
11+
public function getClass(): string
12+
{
13+
return ContainerInterface::class;
14+
}
15+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace LaminasPhpStan\Type\Laminas;
6+
7+
use Psr\Container\ContainerInterface;
8+
9+
final class PsrContainerGetDynamicReturnTypeExtension extends AbstractServiceLocatorGetDynamicReturnTypeExtension
10+
{
11+
public function getClass(): string
12+
{
13+
return ContainerInterface::class;
14+
}
15+
}

src/Type/Laminas/ServiceManagerGetDynamicReturnTypeExtension.php

Lines changed: 1 addition & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -4,106 +4,12 @@
44

55
namespace LaminasPhpStan\Type\Laminas;
66

7-
use Laminas\ServiceManager\AbstractPluginManager;
87
use Laminas\ServiceManager\ServiceLocatorInterface;
9-
use LaminasPhpStan\ServiceManagerLoader;
10-
use PhpParser\Node\Arg;
11-
use PhpParser\Node\Expr\MethodCall;
12-
use PHPStan\Analyser\Scope;
13-
use PHPStan\Reflection\MethodReflection;
14-
use PHPStan\Reflection\ReflectionProvider;
15-
use PHPStan\Type\Constant\ConstantStringType;
16-
use PHPStan\Type\DynamicMethodReturnTypeExtension;
17-
use PHPStan\Type\MixedType;
18-
use PHPStan\Type\NeverType;
19-
use PHPStan\Type\ObjectType;
20-
use PHPStan\Type\Type;
21-
use ReflectionClass;
228

23-
final class ServiceManagerGetDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
9+
final class ServiceManagerGetDynamicReturnTypeExtension extends AbstractServiceLocatorGetDynamicReturnTypeExtension
2410
{
25-
public function __construct(
26-
private ReflectionProvider $reflectionProvider,
27-
private ServiceManagerLoader $serviceManagerLoader
28-
) {
29-
}
30-
3111
public function getClass(): string
3212
{
3313
return ServiceLocatorInterface::class;
3414
}
35-
36-
public function isMethodSupported(MethodReflection $methodReflection): bool
37-
{
38-
return 'get' === $methodReflection->getName();
39-
}
40-
41-
public function getTypeFromMethodCall(
42-
MethodReflection $methodReflection,
43-
MethodCall $methodCall,
44-
Scope $scope
45-
): Type {
46-
$calledOnType = $scope->getType($methodCall->var);
47-
if (! $calledOnType instanceof ObjectType) {
48-
return new MixedType();
49-
}
50-
51-
$args = $methodCall->getArgs();
52-
if (1 !== \count($args)) {
53-
return new MixedType();
54-
}
55-
56-
$serviceManager = $this->serviceManagerLoader->getServiceLocator($calledOnType->getClassName());
57-
58-
$firstArg = $args[0];
59-
if (! $firstArg instanceof Arg) {
60-
throw new \PHPStan\ShouldNotHappenException(\sprintf(
61-
'Argument passed to %s::%s should be a string, %s given',
62-
$methodReflection->getDeclaringClass()->getName(),
63-
$methodReflection->getName(),
64-
$firstArg->getType()
65-
));
66-
}
67-
$argType = $scope->getType($firstArg->value);
68-
if (! $argType instanceof ConstantStringType) {
69-
if ($serviceManager instanceof AbstractPluginManager) {
70-
$refClass = new ReflectionClass($serviceManager);
71-
$refProperty = $refClass->getProperty('instanceOf');
72-
$refProperty->setAccessible(true);
73-
$returnedInstance = $refProperty->getValue($serviceManager);
74-
\assert(null === $returnedInstance || \is_string($returnedInstance));
75-
if (null !== $returnedInstance && $this->reflectionProvider->hasClass($returnedInstance)) {
76-
return new ObjectType($returnedInstance);
77-
}
78-
}
79-
80-
return new MixedType();
81-
}
82-
83-
$serviceName = $argType->getValue();
84-
if (! $serviceManager->has($serviceName)) {
85-
return new NeverType();
86-
}
87-
88-
if (\class_exists($serviceName) || \interface_exists($serviceName)) {
89-
return new ObjectServiceManagerType($serviceName, $serviceName);
90-
}
91-
92-
$service = $serviceManager->get($serviceName);
93-
if (\is_object($service)) {
94-
$className = $service::class;
95-
$refClass = new ReflectionClass($service);
96-
if ($refClass->isAnonymous()) {
97-
if (false !== ($parentClass = $refClass->getParentClass())) {
98-
$className = $parentClass->getName();
99-
} elseif ([] !== ($interfaces = $refClass->getInterfaces())) {
100-
$className = \current($interfaces)->getName();
101-
}
102-
}
103-
104-
return new ObjectServiceManagerType($className, $serviceName);
105-
}
106-
107-
return $scope->getTypeFromValue($service);
108-
}
10915
}

0 commit comments

Comments
 (0)