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 ;
811use PHPStan \Rules \Rule ;
912use PHPStan \Rules \RuleErrorBuilder ;
13+ use PHPStan \Symfony \Service ;
1014use PHPStan \Symfony \ServiceMap ;
1115use PHPStan \TrinaryLogic ;
1216use PHPStan \Type \ObjectType ;
@@ -66,15 +70,30 @@ public function processNode(Node $node, Scope $scope): array
6670 }
6771
6872 $ 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- }
73+ if ($ serviceId === null ) {
74+ return [];
75+ }
76+
77+ /** @var Service $service */
78+ $ service = $ this ->serviceMap ->getService ($ serviceId );
79+ if ($ service === null ) {
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,62 @@ 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 , Service $ 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 ) {
129+ return false ;
130+ }
131+
132+ /** @var ReflectionProperty $classPropertyReflection */
133+ $ classPropertyReflection = $ classProperty ->getNativeReflection ();
134+ $ autowireLocatorAttributes = $ classPropertyReflection
135+ ->getAttributes ('Symfony\Component\DependencyInjection\Attribute\AutowireLocator ' );
136+
137+ return $ this ->isAutowireLocatorService ($ autowireLocatorAttributes , $ service );
138+ }
139+
140+ /**
141+ * @param array<FakeReflectionAttribute|ReflectionAttribute> $autowireLocatorAttributes
142+ */
143+ private function isAutowireLocatorService (array $ autowireLocatorAttributes , Service $ service ): bool
144+ {
145+ foreach ($ autowireLocatorAttributes as $ autowireLocatorAttribute ) {
146+ foreach ($ autowireLocatorAttribute ->getArgumentsExpressions () as $ autowireLocatorServices ) {
147+ foreach ($ autowireLocatorServices ->items as $ autowireLocatorServiceNode ) {
148+ /** @var Node\Expr $autowireLocatorService */
149+ $ autowireLocatorServiceExpr = $ autowireLocatorServiceNode ->value ;
150+
151+ switch (\get_class ($ autowireLocatorServiceExpr )) {
152+ case Node \Scalar \String_::class:
153+ $ autowireLocatorServiceClass = $ autowireLocatorServiceExpr ->value ;
154+ break ;
155+ case Node \Expr \ClassConstFetch::class:
156+ $ autowireLocatorServiceClass = $ autowireLocatorServiceExpr ->class ->toString ();
157+ break ;
158+ default :
159+ $ autowireLocatorServiceClass = null ;
160+ }
161+
162+ if ($ service ->getId () === $ autowireLocatorServiceClass ) {
163+ return true ;
164+ }
165+ }
166+ }
167+ }
168+
169+ return false ;
170+ }
171+
95172}
0 commit comments