55use PhpParser \Node ;
66use PhpParser \Node \Expr \MethodCall ;
77use PHPStan \Analyser \Scope ;
8+ use PHPStan \BetterReflection \Reflection \Adapter \FakeReflectionAttribute ;
9+ use PHPStan \BetterReflection \Reflection \Adapter \ReflectionAttribute ;
10+ use PHPStan \Reflection \ClassReflection ;
811use PHPStan \Rules \Rule ;
912use PHPStan \Rules \RuleErrorBuilder ;
13+ use PHPStan \Symfony \ServiceDefinition ;
1014use PHPStan \Symfony \ServiceMap ;
1115use PHPStan \TrinaryLogic ;
1216use PHPStan \Type \ObjectType ;
1317use PHPStan \Type \Type ;
18+ use Symfony \Component \DependencyInjection \Attribute \AutowireLocator ;
19+ use function class_exists ;
20+ use function get_class ;
1421use 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