2
2
3
3
namespace mglaman \PHPStanDrupal \Rules \Drupal ;
4
4
5
+ use mglaman \PHPStanDrupal \Drupal \ServiceMap ;
5
6
use PhpParser \Node ;
6
7
use PHPStan \Analyser \Scope ;
7
- use PHPStan \Broker \Broker ;
8
- use PHPStan \Reflection \Php \PhpFunctionReflection ;
9
8
use PHPStan \Reflection \ReflectionProvider ;
10
9
use PHPStan \Rules \Rule ;
11
10
use PHPStan \Rules \RuleErrorBuilder ;
12
- use PHPStan \TrinaryLogic ;
13
11
use PHPStan \Type \ClosureType ;
14
12
use PHPStan \Type \Constant \ConstantArrayType ;
15
- use PHPStan \Type \Constant \ConstantArrayTypeAndMethod ;
16
13
use PHPStan \Type \Constant \ConstantIntegerType ;
17
14
use PHPStan \Type \Constant \ConstantStringType ;
18
15
use PHPStan \Type \ObjectType ;
16
+ use PHPStan \Type \Type ;
19
17
use PHPStan \Type \UnionType ;
20
18
use PHPStan \Type \VerbosityLevel ;
21
19
@@ -24,9 +22,12 @@ final class RenderCallbackRule implements Rule
24
22
25
23
protected ReflectionProvider $ reflectionProvider ;
26
24
27
- public function __construct (ReflectionProvider $ reflectionProvider )
25
+ protected ServiceMap $ serviceMap ;
26
+
27
+ public function __construct (ReflectionProvider $ reflectionProvider , ServiceMap $ serviceMap )
28
28
{
29
29
$ this ->reflectionProvider = $ reflectionProvider ;
30
+ $ this ->serviceMap = $ serviceMap ;
30
31
}
31
32
32
33
public function getNodeType (): string
@@ -70,7 +71,7 @@ public function processNode(Node $node, Scope $scope): array
70
71
continue ;
71
72
}
72
73
$ errorLine = $ item ->value ->getLine ();
73
- $ type = $ scope ->getType ($ item ->value );
74
+ $ type = $ this ->getType ($ item ->value , $ scope );
74
75
75
76
if ($ type instanceof ConstantStringType) {
76
77
if (!$ type ->isCallable ()->yes ()) {
@@ -87,17 +88,6 @@ public function processNode(Node $node, Scope $scope): array
87
88
)->line ($ errorLine )
88
89
->tip ('Change record: https://www.drupal.org/node/2966725. ' )
89
90
->build ();
90
- continue ;
91
- }
92
- // @see \PHPStan\Type\Constant\ConstantStringType::isCallable
93
- preg_match ('#^([a-zA-Z_ \\x7f- \\xff \\\\][a-zA-Z0-9_ \\x7f- \\xff \\\\]*)::([a-zA-Z_ \\x7f- \\xff][a-zA-Z0-9_ \\x7f- \\xff]*) \\z# ' , $ type ->getValue (), $ matches );
94
- if ($ matches === null ) {
95
- throw new \PHPStan \ShouldNotHappenException ('Unable to get class name from ConstantStringType value: ' . $ type ->describe (VerbosityLevel::value ()));
96
- }
97
- if (!$ trustedCallbackType ->isSuperTypeOf (new ObjectType ($ matches [1 ]))->yes ()) {
98
- $ errors [] = RuleErrorBuilder::message (
99
- sprintf ("%s callback class '%s' at key '%s' does not implement Drupal\Core\Security\TrustedCallbackInterface. " , $ keyChecked , (new ObjectType ($ matches [1 ]))->describe (VerbosityLevel::value ()), $ pos )
100
- )->line ($ errorLine )->tip ('Change record: https://www.drupal.org/node/2966725. ' )->build ();
101
91
}
102
92
} elseif ($ type instanceof ConstantArrayType) {
103
93
if (!$ type ->isCallable ()->yes ()) {
@@ -139,4 +129,42 @@ public function processNode(Node $node, Scope $scope): array
139
129
140
130
return $ errors ;
141
131
}
132
+
133
+ private function getType (Node \Expr $ node , Scope $ scope ): Type
134
+ {
135
+ $ type = $ scope ->getType ($ node );
136
+ if ($ type instanceof ConstantStringType) {
137
+ if ($ type ->isClassString ()) {
138
+ return $ type ;
139
+ }
140
+ // Covers \Drupal\Core\Controller\ControllerResolver::createController.
141
+ if (substr_count ($ type ->getValue (), ': ' ) === 1 ) {
142
+ [$ class_or_service , $ method ] = explode (': ' , $ type ->getValue (), 2 );
143
+
144
+ $ serviceDefinition = $ this ->serviceMap ->getService ($ class_or_service );
145
+ if ($ serviceDefinition === null || $ serviceDefinition ->getClass () === null ) {
146
+ return $ type ;
147
+ }
148
+ return new ConstantArrayType (
149
+ [new ConstantIntegerType (0 ), new ConstantIntegerType (1 )],
150
+ [
151
+ new ConstantStringType ($ serviceDefinition ->getClass (), true ),
152
+ new ConstantStringType ($ method )
153
+ ]
154
+ );
155
+ }
156
+ // @see \PHPStan\Type\Constant\ConstantStringType::isCallable
157
+ preg_match ('#^([a-zA-Z_ \\x7f- \\xff \\\\][a-zA-Z0-9_ \\x7f- \\xff \\\\]*)::([a-zA-Z_ \\x7f- \\xff][a-zA-Z0-9_ \\x7f- \\xff]*) \\z# ' , $ type ->getValue (), $ matches );
158
+ if ($ matches !== null && count ($ matches ) > 0 ) {
159
+ return new ConstantArrayType (
160
+ [new ConstantIntegerType (0 ), new ConstantIntegerType (1 )],
161
+ [
162
+ new ConstantStringType ($ matches [1 ], true ),
163
+ new ConstantStringType ($ matches [2 ])
164
+ ]
165
+ );
166
+ }
167
+ }
168
+ return $ type ;
169
+ }
142
170
}
0 commit comments