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