Skip to content

Commit 643e000

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 643e000

File tree

1 file changed

+84
-9
lines changed

1 file changed

+84
-9
lines changed

src/Rules/Symfony/ContainerInterfacePrivateServiceRule.php

+84-9
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,12 @@
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\Adapter\ReflectionProperty;
10+
use PHPStan\BetterReflection\Reflection\ReflectionAttribute;
811
use PHPStan\Rules\Rule;
912
use PHPStan\Rules\RuleErrorBuilder;
13+
use PHPStan\Symfony\Service;
1014
use PHPStan\Symfony\ServiceMap;
1115
use PHPStan\TrinaryLogic;
1216
use PHPStan\Type\ObjectType;
@@ -66,15 +70,29 @@ public function processNode(Node $node, Scope $scope): array
6670
}
6771

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

8098
return [];
@@ -92,4 +110,61 @@ private function isServiceSubscriber(Type $containerType, Scope $scope): Trinary
92110
return $isContainerServiceSubscriber->or($serviceSubscriberInterfaceType->isSuperTypeOf($containedClassType));
93111
}
94112

113+
private function isAutowireLocator(Node $node, Scope $scope, Service $service): bool
114+
{
115+
if (!$node->var instanceof Node\Expr\PropertyFetch) {
116+
return false;
117+
}
118+
119+
$containerInterfacePropertyName = $node->var->name->name;
120+
$classProperty = $scope
121+
->getClassReflection()
122+
->getProperty($containerInterfacePropertyName, $scope);
123+
124+
if (!$classProperty) {
125+
return false;
126+
}
127+
128+
/* @var ReflectionProperty $classPropertyReflection */
129+
$classPropertyReflection = $classProperty->getNativeReflection();
130+
$autowireLocatorAttributes = $classPropertyReflection
131+
->getAttributes('Symfony\Component\DependencyInjection\Attribute\AutowireLocator');
132+
133+
return $this->isAutowireLocatorService($autowireLocatorAttributes, $service);
134+
}
135+
136+
/**
137+
* @param array<FakeReflectionAttribute|ReflectionAttribute> $autowireLocatorAttributes
138+
* @param Service $service
139+
* @return bool
140+
*/
141+
private function isAutowireLocatorService(array $autowireLocatorAttributes, Service $service): bool
142+
{
143+
foreach ($autowireLocatorAttributes as $autowireLocatorAttribute) {
144+
foreach ($autowireLocatorAttribute->getArgumentsExpressions() as $autowireLocatorServices) {
145+
foreach ($autowireLocatorServices->items as $autowireLocatorServiceNode) {
146+
/** @var Node\Expr $autowireLocatorService */
147+
$autowireLocatorServiceExpr = $autowireLocatorServiceNode->value;
148+
149+
switch (get_class($autowireLocatorServiceExpr)) {
150+
case Node\Scalar\String_::class:
151+
$autowireLocatorServiceClass = $autowireLocatorServiceExpr->value;
152+
break;
153+
case Node\Expr\ClassConstFetch::class:
154+
$autowireLocatorServiceClass = $autowireLocatorServiceExpr->class->toString();
155+
break;
156+
default:
157+
$autowireLocatorServiceClass = null;
158+
}
159+
160+
if ($service->getId() === $autowireLocatorServiceClass) {
161+
return true;
162+
}
163+
}
164+
}
165+
}
166+
167+
return false;
168+
}
169+
95170
}

0 commit comments

Comments
 (0)