4
4
namespace TheCodingMachine \Safe \PHPStan \Type \Php ;
5
5
6
6
use PhpParser \Node \Expr \FuncCall ;
7
+ use PhpParser \Node \Name ;
7
8
use PHPStan \Analyser \Scope ;
8
9
use PHPStan \Reflection \FunctionReflection ;
9
- use PHPStan \Reflection \ParametersAcceptorSelector ;
10
- use PHPStan \Type \BitwiseFlagHelper ;
11
- use PHPStan \Type \Constant \ConstantStringType ;
12
- use PHPStan \Type \ConstantScalarType ;
13
- use PHPStan \Type \ConstantTypeHelper ;
10
+ use PHPStan \Reflection \ReflectionProvider ;
14
11
use PHPStan \Type \DynamicFunctionReturnTypeExtension ;
15
12
use PHPStan \Type \NeverType ;
16
- use PHPStan \Type \ObjectWithoutClassType ;
13
+ use PHPStan \Type \Php \ JsonThrowOnErrorDynamicReturnTypeExtension ;
17
14
use PHPStan \Type \Type ;
18
- use PHPStan \Type \TypeCombinator ;
19
- use Safe \Exceptions \JsonException ;
20
15
21
16
/**
22
17
* @see \PHPStan\Type\Php\JsonThrowOnErrorDynamicReturnTypeExtension
23
18
*/
24
19
final class JsonDecodeDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension
25
20
{
26
21
public function __construct (
27
- private readonly BitwiseFlagHelper $ bitwiseFlagAnalyser ,
22
+ private readonly JsonThrowOnErrorDynamicReturnTypeExtension $ phpstanCheck ,
23
+ private readonly ReflectionProvider $ reflectionProvider ,
28
24
) {
29
25
}
30
26
@@ -35,74 +31,14 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo
35
31
36
32
public function getTypeFromFunctionCall (FunctionReflection $ functionReflection , FuncCall $ functionCall , Scope $ scope ): Type
37
33
{
38
- $ defaultReturnType = ParametersAcceptorSelector::selectFromArgs (
39
- $ scope ,
40
- $ functionCall ->getArgs (),
41
- $ functionReflection ->getVariants (),
42
- )->getReturnType ();
34
+ $ functionReflection = $ this ->reflectionProvider ->getFunction (new Name ('json_decode ' ), null );
35
+ $ result = $ this ->phpstanCheck ->getTypeFromFunctionCall ($ functionReflection , $ functionCall , $ scope );
43
36
44
- return $ this ->narrowTypeForJsonDecode ($ functionCall , $ scope , $ defaultReturnType );
45
- }
46
-
47
- private function narrowTypeForJsonDecode (FuncCall $ funcCall , Scope $ scope , Type $ fallbackType ): Type
48
- {
49
- $ args = $ funcCall ->getArgs ();
50
- $ isForceArray = $ this ->isForceArray ($ funcCall , $ scope );
51
- if (!isset ($ args [0 ])) {
52
- return $ fallbackType ;
53
- }
54
-
55
- $ firstValueType = $ scope ->getType ($ args [0 ]->value );
56
- if ([] !== $ firstValueType ->getConstantStrings ()) {
57
- $ types = [];
58
-
59
- foreach ($ firstValueType ->getConstantStrings () as $ constantString ) {
60
- $ types [] = $ this ->resolveConstantStringType ($ constantString , $ isForceArray );
61
- }
62
-
63
- return TypeCombinator::union (...$ types );
64
- }
65
-
66
- if ($ isForceArray ) {
67
- return TypeCombinator::remove ($ fallbackType , new ObjectWithoutClassType ());
68
- }
69
-
70
- return $ fallbackType ;
71
- }
72
-
73
- /**
74
- * Is "json_decode(..., true)"?
75
- */
76
- private function isForceArray (FuncCall $ funcCall , Scope $ scope ): bool
77
- {
78
- $ args = $ funcCall ->getArgs ();
79
- if (!isset ($ args [1 ])) {
80
- return false ;
81
- }
82
-
83
- $ secondArgType = $ scope ->getType ($ args [1 ]->value );
84
- $ secondArgValue = 1 === \count ($ secondArgType ->getConstantScalarValues ()) ? $ secondArgType ->getConstantScalarValues ()[0 ] : null ;
85
-
86
- if (is_bool ($ secondArgValue )) {
87
- return $ secondArgValue ;
88
- }
89
-
90
- if ($ secondArgValue !== null || !isset ($ args [3 ])) {
91
- return false ;
92
- }
93
-
94
- // depends on used constants, @see https://www.php.net/manual/en/json.constants.php#constant.json-object-as-array
95
- return $ this ->bitwiseFlagAnalyser ->bitwiseOrContainsConstant ($ args [3 ]->value , $ scope , 'JSON_OBJECT_AS_ARRAY ' )->yes ();
96
- }
97
-
98
- private function resolveConstantStringType (ConstantStringType $ constantStringType , bool $ isForceArray ): Type
99
- {
100
- try {
101
- $ decodedValue = \Safe \json_decode ($ constantStringType ->getValue (), $ isForceArray );
102
- } catch (JsonException ) {
37
+ // if PHPStan reports null and there is a json error, then an invalid constant string was passed
38
+ if ($ result ->isNull ()->yes () && JSON_ERROR_NONE !== json_last_error ()) {
103
39
return new NeverType ();
104
40
}
105
41
106
- return ConstantTypeHelper:: getTypeFromValue ( $ decodedValue ) ;
42
+ return $ result ;
107
43
}
108
44
}
0 commit comments