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 Symfony \Component \DependencyInjection \Attribute \AutowireLocator ;
20+ 21+ use function get_class ;
1422use function sprintf ;
1523
1624/**
@@ -66,15 +74,29 @@ public function processNode(Node $node, Scope $scope): array
6674 }
6775
6876 $ 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- }
77+ if ($ serviceId === null ) {
78+ return [];
79+ }
80+ 81+ $ service = $ this ->serviceMap ->getService ($ serviceId );
82+ if (!$ service instanceof ServiceDefinition) {
83+ return [];
84+ }
85+ 86+ $ isContainerInterfaceType = $ isContainerType ->yes () || $ isPsrContainerType ->yes ();
87+ if (
88+ $ isContainerInterfaceType &&
89+ $ this ->isAutowireLocator ($ node , $ scope , $ service )
90+ ) {
91+ return [];
92+ }
93+ 94+ if (!$ service ->isPublic ()) {
95+ return [
96+ RuleErrorBuilder::message (sprintf ('Service "%s" is private. ' , $ serviceId ))
97+ ->identifier ('symfonyContainer.privateService ' )
98+ ->build (),
99+ ];
78100 }
79101
80102 return [];
@@ -92,4 +114,86 @@ private function isServiceSubscriber(Type $containerType, Scope $scope): Trinary
92114 return $ isContainerServiceSubscriber ->or ($ serviceSubscriberInterfaceType ->isSuperTypeOf ($ containedClassType ));
93115 }
94116
117+ private function isAutowireLocator (Node $ node , Scope $ scope , ServiceDefinition $ service ): bool
118+ {
119+ if (!class_exists ('Symfony \\Component \\DependencyInjection \\Attribute \\AutowireLocator ' )) {
120+ return false ;
121+ }
122+ 123+ if (
124+ !$ node instanceof MethodCall
125+ ) {
126+ return false ;
127+ }
128+ 129+ $ nodeParentProperty = $ node ->var ;
130+ 131+ if (!$ nodeParentProperty instanceof Node \Expr \PropertyFetch) {
132+ return false ;
133+ }
134+ 135+ $ nodeParentPropertyName = $ nodeParentProperty ->name ;
136+ 137+ if (!$ nodeParentPropertyName instanceof Node \Identifier) {
138+ return false ;
139+ }
140+ 141+ $ containerInterfacePropertyName = $ nodeParentPropertyName ->name ;
142+ $ scopeClassReflection = $ scope ->getClassReflection ();
143+ 144+ if (!$ scopeClassReflection instanceof ClassReflection) {
145+ return false ;
146+ }
147+ 148+ $ containerInterfacePropertyReflection = $ scopeClassReflection
149+ ->getProperty ($ containerInterfacePropertyName , $ scope );
150+ 151+ if (!$ containerInterfacePropertyReflection instanceof PhpPropertyReflection) {
152+ return false ;
153+ }
154+ 155+ $ classPropertyReflection = $ containerInterfacePropertyReflection ->getNativeReflection ();
156+ $ autowireLocatorAttributes = $ classPropertyReflection ->getAttributes (AutowireLocator::class);
157+ 158+ return $ this ->isAutowireLocatorService ($ autowireLocatorAttributes , $ service );
159+ }
160+ 161+ /**
162+ * @param array<int, FakeReflectionAttribute|ReflectionAttribute> $autowireLocatorAttributes
163+ */
164+ private function isAutowireLocatorService (array $ autowireLocatorAttributes , ServiceDefinition $ service ): bool
165+ {
166+ foreach ($ autowireLocatorAttributes as $ autowireLocatorAttribute ) {
167+ foreach ($ autowireLocatorAttribute ->getArgumentsExpressions () as $ autowireLocatorServices ) {
168+ if (!$ autowireLocatorServices instanceof Node \Expr \Array_) {
169+ continue ;
170+ }
171+ 172+ foreach ($ autowireLocatorServices ->items as $ autowireLocatorServiceNode ) {
173+ /** @var Node\Expr\ArrayItem $autowireLocatorServiceNode */
174+ $ autowireLocatorServiceExpr = $ autowireLocatorServiceNode ->value ;
175+ 176+ switch (get_class ($ autowireLocatorServiceExpr )) {
177+ case Node \Scalar \String_::class:
178+ $ autowireLocatorServiceClass = $ autowireLocatorServiceExpr ->value ;
179+ break ;
180+ case Node \Expr \ClassConstFetch::class:
181+ $ autowireLocatorServiceClass = $ autowireLocatorServiceExpr ->class instanceof Node \Name
182+ ? $ autowireLocatorServiceExpr ->class ->toString ()
183+ : null ;
184+ break ;
185+ default :
186+ $ autowireLocatorServiceClass = null ;
187+ }
188+ 189+ if ($ service ->getId () === $ autowireLocatorServiceClass ) {
190+ return true ;
191+ }
192+ }
193+ }
194+ }
195+ 196+ return false ;
197+ }
198+ 95199}
0 commit comments