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