Skip to content

Commit 4eb3bee

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 4eb3bee

File tree

1 file changed

+85
-9
lines changed

1 file changed

+85
-9
lines changed

src/Rules/Symfony/ContainerInterfacePrivateServiceRule.php

+85-9
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,13 @@
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;
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;
@@ -66,15 +71,29 @@ public function processNode(Node $node, Scope $scope): array
6671
}
6772

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

8099
return [];
@@ -92,4 +111,61 @@ private function isServiceSubscriber(Type $containerType, Scope $scope): Trinary
92111
return $isContainerServiceSubscriber->or($serviceSubscriberInterfaceType->isSuperTypeOf($containedClassType));
93112
}
94113

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

0 commit comments

Comments
 (0)