5
5
use PhpParser \Node ;
6
6
use PhpParser \Node \Expr \MethodCall ;
7
7
use PHPStan \Analyser \Scope ;
8
+ use PHPStan \BetterReflection \Reflection \Adapter \FakeReflectionAttribute ;
9
+ use PHPStan \BetterReflection \Reflection \Adapter \ReflectionProperty ;
10
+ use PHPStan \BetterReflection \Reflection \ReflectionAttribute ;
8
11
use PHPStan \Rules \Rule ;
9
12
use PHPStan \Rules \RuleErrorBuilder ;
13
+ use PHPStan \Symfony \Service ;
10
14
use PHPStan \Symfony \ServiceMap ;
11
15
use PHPStan \TrinaryLogic ;
12
16
use PHPStan \Type \ObjectType ;
@@ -66,15 +70,29 @@ public function processNode(Node $node, Scope $scope): array
66
70
}
67
71
68
72
$ 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
+ ];
78
96
}
79
97
80
98
return [];
@@ -92,4 +110,61 @@ private function isServiceSubscriber(Type $containerType, Scope $scope): Trinary
92
110
return $ isContainerServiceSubscriber ->or ($ serviceSubscriberInterfaceType ->isSuperTypeOf ($ containedClassType ));
93
111
}
94
112
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
+
95
170
}
0 commit comments