Skip to content

Commit d9243d9

Browse files
authored
Merge pull request #28 from InvisibleSmiley/master
Support Interop and PSR containers for $container->get() resolution
2 parents f5c0da2 + 996c93d commit d9243d9

File tree

13 files changed

+187
-31
lines changed

13 files changed

+187
-31
lines changed

.gitattributes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@
55
.php-cs-fixer.php export-ignore
66
Makefile export-ignore
77
phpstan.neon export-ignore
8+
phpstan-baseline.neon export-ignore
89
phpunit.xml export-ignore

README.md

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,17 @@
1010

1111
This extension provides following features:
1212

13-
1. Provide correct return for `\Laminas\ServiceManager\ServiceLocatorInterface::get()`
14-
1. Handle controller plugins that are called using magic `__call()` in subclasses of
13+
1. Provide correct return type for `$container->get()` calls on containers of type
14+
`\Laminas\ServiceManager\ServiceLocatorInterface`, `\Interop\Container\ContainerInterface` or `\Psr\Container\ContainerInterface`
15+
2. Handle controller plugins that are called using magic `__call()` in subclasses of
1516
`\Laminas\Mvc\Controller\AbstractController`
16-
1. Provide correct return type for `plugin` method of `AbstractController`, `FilterChain`, `PhpRenderer` and `ValidatorChain`
17-
1. `getApplication()`, `getRenderer()`, `getRequest()` and `getResponse()` methods on Controllers, MvcEvents, View,
17+
3. Provide correct return type for `plugin` method of `AbstractController`, `FilterChain`, `PhpRenderer` and `ValidatorChain`
18+
4. `getApplication()`, `getRenderer()`, `getRequest()` and `getResponse()` methods on Controllers, MvcEvents, View,
1819
ViewEvent and Application returns the real instance instead of type-hinted interfaces
19-
1. `getView()` method on `\Laminas\View\Helper\AbstractHelper` returns the real Renderer instance instead of type-hinted
20+
5. `getView()` method on `\Laminas\View\Helper\AbstractHelper` returns the real Renderer instance instead of type-hinted
2021
interface
21-
1. `\Laminas\Stdlib\ArrayObject` is configured as a [Universal object crate](https://phpstan.org/config-reference#universal-object-crates)
22-
1. Handle `\Laminas\Stdlib\AbstractOptions` magic properties
22+
6. `\Laminas\Stdlib\ArrayObject` is configured as a [Universal object crate](https://phpstan.org/config-reference#universal-object-crates)
23+
7. Handle `\Laminas\Stdlib\AbstractOptions` magic properties
2324

2425
## Installation
2526

composer.json

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,37 +19,37 @@
1919
],
2020
"require": {
2121
"php": "^7.4 || ^8.0",
22-
"phpstan/phpstan": "^0.12.88"
22+
"phpstan/phpstan": "^0.12.99"
2323
},
2424
"conflict": {
25-
"laminas/laminas-cache": "<2.11",
25+
"laminas/laminas-cache": "<2.13",
2626
"laminas/laminas-filter": "<2.11",
27-
"laminas/laminas-form": "<2.16",
28-
"laminas/laminas-hydrator": "<4.0",
27+
"laminas/laminas-form": "<2.17",
28+
"laminas/laminas-hydrator": "<4.3",
2929
"laminas/laminas-i18n": "<2.11",
3030
"laminas/laminas-inputfilter": "<2.12",
3131
"laminas/laminas-log": "<2.13",
32-
"laminas/laminas-mail": "<2.14",
32+
"laminas/laminas-mail": "<2.15",
3333
"laminas/laminas-mvc": "<3.2",
3434
"laminas/laminas-paginator": "<2.10",
35-
"laminas/laminas-validator": "<2.14"
35+
"laminas/laminas-validator": "<2.15"
3636
},
3737
"require-dev": {
38-
"laminas/laminas-cache": "^2.11.1",
39-
"laminas/laminas-filter": "^2.11.0",
40-
"laminas/laminas-form": "^2.16.3",
41-
"laminas/laminas-hydrator": "^4.1.0",
42-
"laminas/laminas-i18n": "^2.11.1",
38+
"laminas/laminas-cache": "^2.13.0",
39+
"laminas/laminas-filter": "^2.11.1",
40+
"laminas/laminas-form": "^2.17.0",
41+
"laminas/laminas-hydrator": "^4.3.1",
42+
"laminas/laminas-i18n": "^2.11.2",
4343
"laminas/laminas-inputfilter": "^2.12.0",
4444
"laminas/laminas-log": "^2.13.1",
45-
"laminas/laminas-mail": "^2.14.0",
45+
"laminas/laminas-mail": "^2.15.0",
4646
"laminas/laminas-mvc": "^3.2.0",
4747
"laminas/laminas-paginator": "^2.10.0",
48-
"laminas/laminas-validator": "^2.14.4",
48+
"laminas/laminas-validator": "^2.15.0",
4949
"malukenho/mcbumpface": "^1.1.5",
50-
"phpstan/phpstan-phpunit": "^0.12.19",
51-
"phpunit/phpunit": "^9.5.4",
52-
"slam/php-cs-fixer-extensions": "^v3.0.1",
50+
"phpstan/phpstan-phpunit": "^0.12.22",
51+
"phpunit/phpunit": "^9.5.9",
52+
"slam/php-cs-fixer-extensions": "^v3.1.0",
5353
"slam/php-debug-r": "^v1.7.0"
5454
},
5555
"extra": {

phpstan-baseline.neon

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
parameters:
2+
ignoreErrors:
3+
-
4+
message: "#^Parameter \\#2 \\$args of static method PHPStan\\\\Reflection\\\\ParametersAcceptorSelector\\:\\:selectFromArgs\\(\\) expects array\\<PhpParser\\\\Node\\\\Arg\\>, array\\<PhpParser\\\\Node\\\\Arg\\|PhpParser\\\\Node\\\\VariadicPlaceholder\\> given\\.$#"
5+
count: 1
6+
path: src/Type/Laminas/PluginMethodDynamicReturnTypeExtension/AbstractPluginMethodDynamicReturnTypeExtension.php
7+

phpstan.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
includes:
22
- vendor/phpstan/phpstan-phpunit/extension.neon
3+
- phpstan-baseline.neon
34

45
parameters:
56
level: max

src/Rules/Laminas/ServiceManagerGetMethodCallRule.php

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,19 @@
44

55
namespace LaminasPhpStan\Rules\Laminas;
66

7+
use Interop\Container\ContainerInterface as InteropContainerInterface;
78
use Laminas\ServiceManager\AbstractPluginManager;
89
use Laminas\ServiceManager\ServiceLocatorInterface;
910
use LaminasPhpStan\ServiceManagerLoader;
1011
use LaminasPhpStan\Type\Laminas\ObjectServiceManagerType;
1112
use PhpParser\Node;
13+
use PhpParser\Node\Arg;
1214
use PHPStan\Analyser\Scope;
1315
use PHPStan\Broker\Broker;
1416
use PHPStan\Rules\Rule;
1517
use PHPStan\Type\Constant\ConstantStringType;
1618
use PHPStan\Type\ObjectType;
19+
use Psr\Container\ContainerInterface as PsrContainerInterface;
1720
use ReflectionClass;
1821

1922
/**
@@ -47,13 +50,17 @@ public function processNode(Node $node, Scope $scope): array
4750
return [];
4851
}
4952

50-
$argType = $scope->getType($node->args[0]->value);
53+
$firstArg = $node->args[0];
54+
if (! $firstArg instanceof Arg) {
55+
return [];
56+
}
57+
$argType = $scope->getType($firstArg->value);
5158
if (! $argType instanceof ConstantStringType) {
5259
return [];
5360
}
5461

5562
$calledOnType = $scope->getType($node->var);
56-
if (! $calledOnType instanceof ObjectType || ! $calledOnType->isInstanceOf(ServiceLocatorInterface::class)->yes()) {
63+
if (! $calledOnType instanceof ObjectType || ! $this->isTypeInstanceOfContainer($calledOnType)) {
5764
return [];
5865
}
5966

@@ -97,4 +104,11 @@ public function processNode(Node $node, Scope $scope): array
97104
$classDoesNotExistNote
98105
)];
99106
}
107+
108+
private function isTypeInstanceOfContainer(ObjectType $type): bool
109+
{
110+
return $type->isInstanceOf(ServiceLocatorInterface::class)->yes()
111+
|| $type->isInstanceOf(InteropContainerInterface::class)->yes()
112+
|| $type->isInstanceOf(PsrContainerInterface::class)->yes();
113+
}
100114
}

src/Type/Laminas/PluginMethodDynamicReturnTypeExtension/AbstractPluginMethodDynamicReturnTypeExtension.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace LaminasPhpStan\Type\Laminas\PluginMethodDynamicReturnTypeExtension;
66

77
use LaminasPhpStan\ServiceManagerLoader;
8+
use PhpParser\Node\Arg;
89
use PhpParser\Node\Expr\MethodCall;
910
use PHPStan\Analyser\Scope;
1011
use PHPStan\Reflection\MethodReflection;
@@ -35,7 +36,16 @@ final public function getTypeFromMethodCall(
3536
MethodCall $methodCall,
3637
Scope $scope
3738
): Type {
38-
$argType = $scope->getType($methodCall->args[0]->value);
39+
$firstArg = $methodCall->args[0];
40+
if (! $firstArg instanceof Arg) {
41+
throw new \PHPStan\ShouldNotHappenException(\sprintf(
42+
'Argument passed to %s::%s should be a string, %s given',
43+
$methodReflection->getDeclaringClass()->getName(),
44+
$methodReflection->getName(),
45+
$firstArg->getType()
46+
));
47+
}
48+
$argType = $scope->getType($firstArg->value);
3949
$strings = TypeUtils::getConstantStrings($argType);
4050
$plugin = 1 === \count($strings) ? $strings[0]->getValue() : null;
4151

src/Type/Laminas/ServiceManagerGetDynamicReturnTypeExtension.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Laminas\ServiceManager\AbstractPluginManager;
88
use Laminas\ServiceManager\ServiceLocatorInterface;
99
use LaminasPhpStan\ServiceManagerLoader;
10+
use PhpParser\Node\Arg;
1011
use PhpParser\Node\Expr\MethodCall;
1112
use PHPStan\Analyser\Scope;
1213
use PHPStan\Broker\Broker;
@@ -62,7 +63,16 @@ public function getTypeFromMethodCall(
6263

6364
$serviceManager = $this->serviceManagerLoader->getServiceLocator($calledOnType->getClassName());
6465

65-
$argType = $scope->getType($methodCall->args[0]->value);
66+
$firstArg = $methodCall->args[0];
67+
if (! $firstArg instanceof Arg) {
68+
throw new \PHPStan\ShouldNotHappenException(\sprintf(
69+
'Argument passed to %s::%s should be a string, %s given',
70+
$methodReflection->getDeclaringClass()->getName(),
71+
$methodReflection->getName(),
72+
$firstArg->getType()
73+
));
74+
}
75+
$argType = $scope->getType($firstArg->value);
6676
if (! $argType instanceof ConstantStringType) {
6777
if ($serviceManager instanceof AbstractPluginManager) {
6878
$refClass = new ReflectionClass($serviceManager);

tests/Rules/Laminas/PluginManagerGetMethodCallRuleTest.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
/**
1414
* @covers \LaminasPhpStan\Rules\Laminas\ServiceManagerGetMethodCallRule
15+
* @extends RuleTestCase<ServiceManagerGetMethodCallRule>
1516
*/
1617
final class PluginManagerGetMethodCallRuleTest extends RuleTestCase
1718
{
@@ -22,6 +23,9 @@ protected function setUp(): void
2223
$this->serviceManagerLoader = new ServiceManagerLoader(null);
2324
}
2425

26+
/**
27+
* @return Rule<\PhpParser\Node\Expr\MethodCall>
28+
*/
2529
protected function getRule(): Rule
2630
{
2731
return new ServiceManagerGetMethodCallRule($this->createBroker(), $this->serviceManagerLoader);
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace LaminasPhpStan\Tests\Rules\Laminas\ServiceManagerGetMethodCallRule;
6+
7+
use Interop\Container\ContainerInterface;
8+
use Laminas\Form\FormElementManager;
9+
use Laminas\Mvc\Controller\ControllerManager;
10+
use stdClass;
11+
12+
final class InteropContainerFoo
13+
{
14+
private ContainerInterface $container;
15+
16+
public function __construct(ContainerInterface $container)
17+
{
18+
$this->container = $container;
19+
}
20+
21+
public function foo(): void
22+
{
23+
$this->container->get('non_existent_service');
24+
25+
$this->container->get('EventManager');
26+
$this->container->get('foo', 'bar');
27+
$this->container->get([]);
28+
29+
$getterName = 'get';
30+
$this->container->{$getterName}('EventManager');
31+
$this->container->has('EventManager');
32+
33+
$stdClass = new stdClass();
34+
$stdClass->get('non_existent_service');
35+
36+
$this->container->get(ControllerManager::class);
37+
$this->container->get(FormElementManager::class);
38+
}
39+
40+
public function get(string $foo): void
41+
{
42+
}
43+
}

0 commit comments

Comments
 (0)