Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit b49fdc7

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 b49fdc7

File tree

1 file changed

+107
-9
lines changed

1 file changed

+107
-9
lines changed

‎src/Rules/Symfony/ContainerInterfacePrivateServiceRule.php‎

Lines changed: 107 additions & 9 deletions
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\Adapter\ReflectionAttribute;
10+
use PHPStan\Reflection\ClassReflection;
811
use PHPStan\Rules\Rule;
912
use PHPStan\Rules\RuleErrorBuilder;
13+
use PHPStan\Symfony\ServiceDefinition;
1014
use PHPStan\Symfony\ServiceMap;
1115
use PHPStan\TrinaryLogic;
1216
use PHPStan\Type\ObjectType;
1317
use PHPStan\Type\Type;
18+
use Symfony\Component\DependencyInjection\Attribute\AutowireLocator;
19+
use function class_exists;
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,81 @@ 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 (!class_exists('Symfony\\Component\\DependencyInjection\\Attribute\\AutowireLocator')) {
119+
return false;
120+
}
121+
122+
if (
123+
!$node instanceof MethodCall
124+
) {
125+
return false;
126+
}
127+
128+
$nodeParentProperty = $node->var;
129+
130+
if (!$nodeParentProperty instanceof Node\Expr\PropertyFetch) {
131+
return false;
132+
}
133+
134+
$nodeParentPropertyName = $nodeParentProperty->name;
135+
136+
if (!$nodeParentPropertyName instanceof Node\Identifier) {
137+
return false;
138+
}
139+
140+
$containerInterfacePropertyName = $nodeParentPropertyName->name;
141+
$scopeClassReflection = $scope->getClassReflection();
142+
143+
if (!$scopeClassReflection instanceof ClassReflection) {
144+
return false;
145+
}
146+
147+
$containerInterfacePropertyReflection = $scopeClassReflection
148+
->getNativeProperty($containerInterfacePropertyName);
149+
$classPropertyReflection = $containerInterfacePropertyReflection->getNativeReflection();
150+
$autowireLocatorAttributes = $classPropertyReflection->getAttributes(AutowireLocator::class);
151+
152+
return $this->isAutowireLocatorService($autowireLocatorAttributes, $service);
153+
}
154+
155+
/**
156+
* @param array<int, FakeReflectionAttribute|ReflectionAttribute> $autowireLocatorAttributes
157+
*/
158+
private function isAutowireLocatorService(array $autowireLocatorAttributes, ServiceDefinition $service): bool
159+
{
160+
foreach ($autowireLocatorAttributes as $autowireLocatorAttribute) {
161+
foreach ($autowireLocatorAttribute->getArgumentsExpressions() as $autowireLocatorServices) {
162+
if (!$autowireLocatorServices instanceof Node\Expr\Array_) {
163+
continue;
164+
}
165+
166+
foreach ($autowireLocatorServices->items as $autowireLocatorServiceNode) {
167+
/** @var Node\Expr\ArrayItem $autowireLocatorServiceNode */
168+
$autowireLocatorServiceExpr = $autowireLocatorServiceNode->value;
169+
170+
switch (get_class($autowireLocatorServiceExpr)) {
171+
case Node\Scalar\String_::class:
172+
$autowireLocatorServiceClass = $autowireLocatorServiceExpr->value;
173+
break;
174+
case Node\Expr\ClassConstFetch::class:
175+
$autowireLocatorServiceClass = $autowireLocatorServiceExpr->class instanceof Node\Name
176+
? $autowireLocatorServiceExpr->class->toString()
177+
: null;
178+
break;
179+
default:
180+
$autowireLocatorServiceClass = null;
181+
}
182+
183+
if ($service->getId() === $autowireLocatorServiceClass) {
184+
return true;
185+
}
186+
}
187+
}
188+
}
189+
190+
return false;
191+
}
192+
95193
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /