Skip to content

Commit e46906a

Browse files
committed
Basic support for simple Symfony #[AutowireLocator] attribute
https://symfony.com/blog/new-in-symfony-6-4-autowirelocator-and-autowireiterator-attributes
1 parent c7b7e7f commit e46906a

File tree

1 file changed

+111
-9
lines changed

1 file changed

+111
-9
lines changed

src/Rules/Symfony/ContainerInterfacePrivateServiceRule.php

+111-9
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,19 @@
55
use PhpParser\Node;
66
use PhpParser\Node\Expr\MethodCall;
77
use PHPStan\Analyser\Scope;
8+
use PHPStan\BetterReflection\Reflection\Adapter\FakeReflectionAttribute;
9+
use PHPStan\BetterReflection\Reflection\ReflectionAttribute;
10+
use PHPStan\Reflection\ClassReflection;
11+
use PHPStan\Reflection\Php\PhpPropertyReflection;
12+
use PHPStan\Reflection\PropertyReflection;
813
use PHPStan\Rules\Rule;
914
use PHPStan\Rules\RuleErrorBuilder;
15+
use PHPStan\Symfony\ServiceDefinition;
1016
use PHPStan\Symfony\ServiceMap;
1117
use PHPStan\TrinaryLogic;
1218
use PHPStan\Type\ObjectType;
1319
use PHPStan\Type\Type;
20+
use function get_class;
1421
use function sprintf;
1522

1623
/**
@@ -66,15 +73,29 @@ public function processNode(Node $node, Scope $scope): array
6673
}
6774

6875
$serviceId = $this->serviceMap::getServiceIdFromNode($node->getArgs()[0]->value, $scope);
69-
if ($serviceId !== null) {
70-
$service = $this->serviceMap->getService($serviceId);
71-
if ($service !== null && !$service->isPublic()) {
72-
return [
73-
RuleErrorBuilder::message(sprintf('Service "%s" is private.', $serviceId))
74-
->identifier('symfonyContainer.privateService')
75-
->build(),
76-
];
77-
}
76+
if ($serviceId === null) {
77+
return [];
78+
}
79+
80+
$service = $this->serviceMap->getService($serviceId);
81+
if (!$service instanceof ServiceDefinition) {
82+
return [];
83+
}
84+
85+
$isContainerInterfaceType = $isContainerType->yes() || $isPsrContainerType->yes();
86+
if (
87+
$isContainerInterfaceType &&
88+
$this->isAutowireLocator($node, $scope, $service)
89+
) {
90+
return [];
91+
}
92+
93+
if (!$service->isPublic()) {
94+
return [
95+
RuleErrorBuilder::message(sprintf('Service "%s" is private.', $serviceId))
96+
->identifier('symfonyContainer.privateService')
97+
->build(),
98+
];
7899
}
79100

80101
return [];
@@ -92,4 +113,85 @@ private function isServiceSubscriber(Type $containerType, Scope $scope): Trinary
92113
return $isContainerServiceSubscriber->or($serviceSubscriberInterfaceType->isSuperTypeOf($containedClassType));
93114
}
94115

116+
private function isAutowireLocator(Node $node, Scope $scope, ServiceDefinition $service): bool
117+
{
118+
if (
119+
!$node instanceof MethodCall
120+
) {
121+
return false;
122+
}
123+
124+
$nodeParentProperty = $node->var;
125+
126+
if (!$nodeParentProperty instanceof Node\Expr\PropertyFetch) {
127+
return false;
128+
}
129+
130+
$nodeParentPropertyName = $nodeParentProperty->name;
131+
132+
if (!$nodeParentPropertyName instanceof Node\Identifier) {
133+
return false;
134+
}
135+
136+
$containerInterfacePropertyName = $nodeParentPropertyName->name;
137+
$scopeClassReflection = $scope->getClassReflection();
138+
139+
if (!$scopeClassReflection instanceof ClassReflection) {
140+
return false;
141+
}
142+
143+
/** @var PropertyReflection $containerInterfacePropertyReflection */
144+
$containerInterfacePropertyReflection = $scopeClassReflection
145+
->getProperty($containerInterfacePropertyName, $scope);
146+
147+
if (!$containerInterfacePropertyReflection instanceof PhpPropertyReflection) {
148+
return false;
149+
}
150+
151+
$classPropertyReflection = $containerInterfacePropertyReflection->getNativeReflection();
152+
/** @var class-string $autowireLocatorClassString */
153+
$autowireLocatorClassString = 'Symfony\Component\DependencyInjection\Attribute\AutowireLocator';
154+
$autowireLocatorAttributes = $classPropertyReflection->getAttributes($autowireLocatorClassString);
155+
156+
return $this->isAutowireLocatorService($autowireLocatorAttributes, $service);
157+
}
158+
159+
/**
160+
* @param array<int, FakeReflectionAttribute|ReflectionAttribute> $autowireLocatorAttributes
161+
*/
162+
private function isAutowireLocatorService(array $autowireLocatorAttributes, ServiceDefinition $service): bool
163+
{
164+
foreach ($autowireLocatorAttributes as $autowireLocatorAttribute) {
165+
foreach ($autowireLocatorAttribute->getArgumentsExpressions() as $autowireLocatorServices) {
166+
if (!$autowireLocatorServices instanceof Node\Expr\Array_) {
167+
continue;
168+
}
169+
170+
foreach ($autowireLocatorServices->items as $autowireLocatorServiceNode) {
171+
/** @var Node\Expr $autowireLocatorService */
172+
$autowireLocatorServiceExpr = $autowireLocatorServiceNode->value;
173+
174+
switch (get_class($autowireLocatorServiceExpr)) {
175+
case Node\Scalar\String_::class:
176+
$autowireLocatorServiceClass = $autowireLocatorServiceExpr->value;
177+
break;
178+
case Node\Expr\ClassConstFetch::class:
179+
$autowireLocatorServiceClass = $autowireLocatorServiceExpr->class instanceof Node\Name
180+
? $autowireLocatorServiceExpr->class->toString()
181+
: null;
182+
break;
183+
default:
184+
$autowireLocatorServiceClass = null;
185+
}
186+
187+
if ($service->getId() === $autowireLocatorServiceClass) {
188+
return true;
189+
}
190+
}
191+
}
192+
}
193+
194+
return false;
195+
}
196+
95197
}

0 commit comments

Comments
 (0)