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 \ReflectionProperty ;
10+ use PHPStan \BetterReflection \Reflection \ReflectionAttribute ;
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 ;
@@ -66,15 +71,29 @@ public function processNode(Node $node, Scope $scope): array
6671 }
6772
6873 $ 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- }
74+ if ($ serviceId === null ) {
75+ return [];
76+ }
77+
78+ $ service = $ this ->serviceMap ->getService ($ serviceId );
79+ if (!$ service instanceof ServiceDefinition) {
80+ return [];
81+ }
82+
83+ $ isContainerInterfaceType = $ isContainerType ->yes () || $ isPsrContainerType ->yes ();
84+ if (
85+ $ isContainerInterfaceType &&
86+ $ this ->isAutowireLocator ($ node , $ scope , $ service )
87+ ) {
88+ return [];
89+ }
90+
91+ if (!$ service ->isPublic ()) {
92+ return [
93+ RuleErrorBuilder::message (sprintf ('Service "%s" is private. ' , $ serviceId ))
94+ ->identifier ('symfonyContainer.privateService ' )
95+ ->build (),
96+ ];
7897 }
7998
8099 return [];
@@ -92,4 +111,61 @@ private function isServiceSubscriber(Type $containerType, Scope $scope): Trinary
92111 return $ isContainerServiceSubscriber ->or ($ serviceSubscriberInterfaceType ->isSuperTypeOf ($ containedClassType ));
93112 }
94113
114+ private function isAutowireLocator (Node $ node , Scope $ scope , ServiceDefinition $ service ): bool
115+ {
116+ if (
117+ !$ node instanceof MethodCall ||
118+ !$ node ->var instanceof Node \Expr \PropertyFetch
119+ ) {
120+ return false ;
121+ }
122+
123+ $ containerInterfacePropertyName = $ node ->var ->name ->name ;
124+ $ classProperty = $ scope
125+ ->getClassReflection ()
126+ ->getProperty ($ containerInterfacePropertyName , $ scope );
127+
128+ if (!$ classProperty instanceof PhpPropertyReflection) {
129+ return false ;
130+ }
131+
132+ $ classPropertyReflection = $ classProperty ->getNativeReflection ();
133+ $ autowireLocatorAttributes = $ classPropertyReflection
134+ ->getAttributes ('Symfony\Component\DependencyInjection\Attribute\AutowireLocator ' );
135+
136+ return $ this ->isAutowireLocatorService ($ autowireLocatorAttributes , $ service );
137+ }
138+
139+ /**
140+ * @param array<FakeReflectionAttribute|ReflectionAttribute> $autowireLocatorAttributes
141+ */
142+ private function isAutowireLocatorService (array $ autowireLocatorAttributes , ServiceDefinition $ service ): bool
143+ {
144+ foreach ($ autowireLocatorAttributes as $ autowireLocatorAttribute ) {
145+ foreach ($ autowireLocatorAttribute ->getArgumentsExpressions () as $ autowireLocatorServices ) {
146+ foreach ($ autowireLocatorServices ->items as $ autowireLocatorServiceNode ) {
147+ /** @var Node\Expr $autowireLocatorService */
148+ $ autowireLocatorServiceExpr = $ autowireLocatorServiceNode ->value ;
149+
150+ switch (\get_class ($ autowireLocatorServiceExpr )) {
151+ case Node \Scalar \String_::class:
152+ $ autowireLocatorServiceClass = $ autowireLocatorServiceExpr ->value ;
153+ break ;
154+ case Node \Expr \ClassConstFetch::class:
155+ $ autowireLocatorServiceClass = $ autowireLocatorServiceExpr ->class ->toString ();
156+ break ;
157+ default :
158+ $ autowireLocatorServiceClass = null ;
159+ }
160+
161+ if ($ service ->getId () === $ autowireLocatorServiceClass ) {
162+ return true ;
163+ }
164+ }
165+ }
166+ }
167+
168+ return false ;
169+ }
170+
95171}
0 commit comments