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 ;
812use PHPStan \Rules \Rule ;
913use PHPStan \Rules \RuleErrorBuilder ;
14+ use PHPStan \Symfony \ServiceDefinition ;
1015use PHPStan \Symfony \ServiceMap ;
1116use PHPStan \TrinaryLogic ;
1217use PHPStan \Type \ObjectType ;
1318use PHPStan \Type \Type ;
19+ use function get_class ;
1420use function sprintf ;
1521
1622/**
@@ -66,15 +72,29 @@ public function processNode(Node $node, Scope $scope): array
6672 }
6773
6874 $ 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- }
75+ if ($ serviceId === null ) {
76+ return [];
77+ }
78+
79+ $ service = $ this ->serviceMap ->getService ($ serviceId );
80+ if (!$ service instanceof ServiceDefinition) {
81+ return [];
82+ }
83+
84+ $ isContainerInterfaceType = $ isContainerType ->yes () || $ isPsrContainerType ->yes ();
85+ if (
86+ $ isContainerInterfaceType &&
87+ $ this ->isAutowireLocator ($ node , $ scope , $ service )
88+ ) {
89+ return [];
90+ }
91+
92+ if (!$ service ->isPublic ()) {
93+ return [
94+ RuleErrorBuilder::message (sprintf ('Service "%s" is private. ' , $ serviceId ))
95+ ->identifier ('symfonyContainer.privateService ' )
96+ ->build (),
97+ ];
7898 }
7999
80100 return [];
@@ -92,4 +112,84 @@ private function isServiceSubscriber(Type $containerType, Scope $scope): Trinary
92112 return $ isContainerServiceSubscriber ->or ($ serviceSubscriberInterfaceType ->isSuperTypeOf ($ containedClassType ));
93113 }
94114
115+ private function isAutowireLocator (Node $ node , Scope $ scope , ServiceDefinition $ service ): bool
116+ {
117+ if (
118+ !$ node instanceof MethodCall
119+ ) {
120+ return false ;
121+ }
122+
123+ $ nodeParentProperty = $ node ->var ;
124+
125+ if (!$ nodeParentProperty instanceof Node \Expr \PropertyFetch) {
126+ return false ;
127+ }
128+
129+ $ nodeParentPropertyName = $ nodeParentProperty ->name ;
130+
131+ if (!$ nodeParentPropertyName instanceof Node \Identifier) {
132+ return false ;
133+ }
134+
135+ $ containerInterfacePropertyName = $ nodeParentPropertyName ->name ;
136+ $ scopeClassReflection = $ scope ->getClassReflection ();
137+
138+ if (!$ scopeClassReflection instanceof ClassReflection) {
139+ return false ;
140+ }
141+
142+ $ containerInterfacePropertyReflection = $ scopeClassReflection
143+ ->getProperty ($ containerInterfacePropertyName , $ scope );
144+
145+ if (!$ containerInterfacePropertyReflection instanceof PhpPropertyReflection) {
146+ return false ;
147+ }
148+
149+ $ classPropertyReflection = $ containerInterfacePropertyReflection ->getNativeReflection ();
150+ /** @var class-string $autowireLocatorClassString */
151+ $ autowireLocatorClassString = 'Symfony\Component\DependencyInjection\Attribute\AutowireLocator ' ;
152+ $ autowireLocatorAttributes = $ classPropertyReflection ->getAttributes ($ autowireLocatorClassString );
153+
154+ return $ this ->isAutowireLocatorService ($ autowireLocatorAttributes , $ service );
155+ }
156+
157+ /**
158+ * @param array<int, FakeReflectionAttribute|ReflectionAttribute> $autowireLocatorAttributes
159+ */
160+ private function isAutowireLocatorService (array $ autowireLocatorAttributes , ServiceDefinition $ service ): bool
161+ {
162+ foreach ($ autowireLocatorAttributes as $ autowireLocatorAttribute ) {
163+ foreach ($ autowireLocatorAttribute ->getArgumentsExpressions () as $ autowireLocatorServices ) {
164+ if (!$ autowireLocatorServices instanceof Node \Expr \Array_) {
165+ continue ;
166+ }
167+
168+ foreach ($ autowireLocatorServices ->items as $ autowireLocatorServiceNode ) {
169+ /** @var Node\Expr $autowireLocatorService */
170+ $ autowireLocatorServiceExpr = $ autowireLocatorServiceNode ->value ;
171+
172+ switch (get_class ($ autowireLocatorServiceExpr )) {
173+ case Node \Scalar \String_::class:
174+ $ autowireLocatorServiceClass = $ autowireLocatorServiceExpr ->value ;
175+ break ;
176+ case Node \Expr \ClassConstFetch::class:
177+ $ autowireLocatorServiceClass = $ autowireLocatorServiceExpr ->class instanceof Node \Name
178+ ? $ autowireLocatorServiceExpr ->class ->toString ()
179+ : null ;
180+ break ;
181+ default :
182+ $ autowireLocatorServiceClass = null ;
183+ }
184+
185+ if ($ service ->getId () === $ autowireLocatorServiceClass ) {
186+ return true ;
187+ }
188+ }
189+ }
190+ }
191+
192+ return false ;
193+ }
194+
95195}
0 commit comments