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 ;
11
+ use PHPStan \Reflection \Php \PhpPropertyReflection ;
8
12
use PHPStan \Rules \Rule ;
9
13
use PHPStan \Rules \RuleErrorBuilder ;
14
+ use PHPStan \Symfony \ServiceDefinition ;
10
15
use PHPStan \Symfony \ServiceMap ;
11
16
use PHPStan \TrinaryLogic ;
12
17
use PHPStan \Type \ObjectType ;
@@ -66,15 +71,29 @@ public function processNode(Node $node, Scope $scope): array
66
71
}
67
72
68
73
$ 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
+ ];
78
97
}
79
98
80
99
return [];
@@ -92,4 +111,61 @@ private function isServiceSubscriber(Type $containerType, Scope $scope): Trinary
92
111
return $ isContainerServiceSubscriber ->or ($ serviceSubscriberInterfaceType ->isSuperTypeOf ($ containedClassType ));
93
112
}
94
113
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
+
95
171
}
0 commit comments