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 ;
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,83 @@ 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+ $ autowireLocatorAttributes = $ classPropertyReflection
151+ ->getAttributes ('Symfony \\Component \\DependencyInjection \\Attribute \\AutowireLocator ' );
152+
153+ return $ this ->isAutowireLocatorService ($ autowireLocatorAttributes , $ service );
154+ }
155+
156+ /**
157+ * @param array<int, FakeReflectionAttribute|ReflectionAttribute> $autowireLocatorAttributes
158+ */
159+ private function isAutowireLocatorService (array $ autowireLocatorAttributes , ServiceDefinition $ service ): bool
160+ {
161+ foreach ($ autowireLocatorAttributes as $ autowireLocatorAttribute ) {
162+ foreach ($ autowireLocatorAttribute ->getArgumentsExpressions () as $ autowireLocatorServices ) {
163+ if (!$ autowireLocatorServices instanceof Node \Expr \Array_) {
164+ continue ;
165+ }
166+
167+ foreach ($ autowireLocatorServices ->items as $ autowireLocatorServiceNode ) {
168+ /** @var Node\Expr\ArrayItem $autowireLocatorServiceNode */
169+ $ autowireLocatorServiceExpr = $ autowireLocatorServiceNode ->value ;
170+
171+ switch (get_class ($ autowireLocatorServiceExpr )) {
172+ case Node \Scalar \String_::class:
173+ $ autowireLocatorServiceClass = $ autowireLocatorServiceExpr ->value ;
174+ break ;
175+ case Node \Expr \ClassConstFetch::class:
176+ $ autowireLocatorServiceClass = $ autowireLocatorServiceExpr ->class instanceof Node \Name
177+ ? $ autowireLocatorServiceExpr ->class ->toString ()
178+ : null ;
179+ break ;
180+ default :
181+ $ autowireLocatorServiceClass = null ;
182+ }
183+
184+ if ($ service ->getId () === $ autowireLocatorServiceClass ) {
185+ return true ;
186+ }
187+ }
188+ }
189+ }
190+
191+ return false ;
192+ }
193+
95194}
0 commit comments