Skip to content

Commit 964eca5

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 964eca5

File tree

1 file changed

+109
-9
lines changed

1 file changed

+109
-9
lines changed

src/Rules/Symfony/ContainerInterfacePrivateServiceRule.php

+109-9
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,18 @@
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;
812
use PHPStan\Rules\Rule;
913
use PHPStan\Rules\RuleErrorBuilder;
14+
use PHPStan\Symfony\ServiceDefinition;
1015
use PHPStan\Symfony\ServiceMap;
1116
use PHPStan\TrinaryLogic;
1217
use PHPStan\Type\ObjectType;
1318
use PHPStan\Type\Type;
19+
use function get_class;
1420
use function sprintf;
1521

1622
/**
@@ -66,15 +72,29 @@ public function processNode(Node $node, Scope $scope): array
6672
}
6773

6874
$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-
}
75+
if ($serviceId === null) {
76+
return [];
77+
}
78+
79+
$service = $this->serviceMap->getService($serviceId);
80+
if (!$service instanceof ServiceDefinition) {
81+
return [];
82+
}
83+
84+
$isContainerInterfaceType = $isContainerType->yes() || $isPsrContainerType->yes();
85+
if (
86+
$isContainerInterfaceType &&
87+
$this->isAutowireLocator($node, $scope, $service)
88+
) {
89+
return [];
90+
}
91+
92+
if (!$service->isPublic()) {
93+
return [
94+
RuleErrorBuilder::message(sprintf('Service "%s" is private.', $serviceId))
95+
->identifier('symfonyContainer.privateService')
96+
->build(),
97+
];
7898
}
7999

80100
return [];
@@ -92,4 +112,84 @@ private function isServiceSubscriber(Type $containerType, Scope $scope): Trinary
92112
return $isContainerServiceSubscriber->or($serviceSubscriberInterfaceType->isSuperTypeOf($containedClassType));
93113
}
94114

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

0 commit comments

Comments
 (0)