22
33namespace PHPStan \Type \Php ;
44
5+ <<<<<<< HEAD
56<<<<<<< HEAD
67=======
78use PhpParser \Node \Arg ;
9+ =======
10+ use PhpParser \ConstExprEvaluator ;
11+ >>>>>>> check for JSON_OBJECT_AS_ARRAY , in case of null and array
812use PhpParser \Node \Expr;
913use PhpParser \Node \Expr \BinaryOp \BitwiseOr ;
1014use PhpParser \Node \Expr \ConstFetch ;
3539use PHPStan \Type \Type ;
3640use PHPStan \Type \TypeCombinator ;
3741use stdClass ;
42+ use function constant ;
3843use function json_decode ;
44+ use const JSON_OBJECT_AS_ARRAY ;
3945
4046class JsonThrowOnErrorDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension
4147{
@@ -94,17 +100,17 @@ public function getTypeFromFunctionCall(
94100 private function narrowTypeForJsonDecode (FuncCall $ funcCall , Scope $ scope ): Type
95101 {
96102 $ args = $ funcCall ->getArgs ();
97- $ isForceArray = $ this ->isForceArray ($ funcCall );
103+ $ isArrayWithoutStdClass = $ this ->isForceArrayWithoutStdClass ($ funcCall );
98104
99105 $ firstArgValue = $ args [0 ]->value ;
100106 $ firstValueType = $ scope ->getType ($ firstArgValue );
101107
102108 if ($ firstValueType instanceof ConstantStringType) {
103- return $ this ->resolveConstantStringType ($ firstValueType , $ isForceArray );
109+ return $ this ->resolveConstantStringType ($ firstValueType , $ isArrayWithoutStdClass );
104110 }
105111
106112 // fallback type
107- if ($ isForceArray ) {
113+ if ($ isArrayWithoutStdClass ) {
108114 return new MixedType (true , new ObjectType (stdClass::class));
109115 }
110116
@@ -114,20 +120,45 @@ private function narrowTypeForJsonDecode(FuncCall $funcCall, Scope $scope): Type
114120 /**
115121 * Is "json_decode(..., true)"?
116122 */
117- private function isForceArray (FuncCall $ funcCall ): bool
123+ private function isForceArrayWithoutStdClass (FuncCall $ funcCall ): bool
118124 {
119125 $ args = $ funcCall ->getArgs ();
120126
121- if (! isset ($ args [1 ])) {
122- return false ;
123- }
124-
125- $ secondArgValue = $ args [1 ]->value ;
126- if (! $ secondArgValue instanceof ConstFetch) {
127- return false ;
127+ $ constExprEvaluator = new ConstExprEvaluator (static function (Expr $ expr ) {
128+ if ($ expr instanceof ConstFetch) {
129+ return constant ($ expr ->name ->toString ());
130+ }
131+
132+ return null ;
133+ });
134+
135+ if (isset ($ args [1 ])) {
136+ $ secondArgValue = $ args [1 ]->value ;
137+
138+ $ constValue = $ constExprEvaluator ->evaluateSilently ($ secondArgValue );
139+ if ($ constValue === true ) {
140+ return true ;
141+ }
142+
143+ if ($ constValue === false ) {
144+ return false ;
145+ }
146+
147+ // depends on used constants
148+ if ($ constValue === null ) {
149+ if (! isset ($ args [3 ])) {
150+ return false ;
151+ }
152+
153+ // @see https://www.php.net/manual/en/json.constants.php#constant.json-object-as-array
154+ $ thirdArgValue = $ constExprEvaluator ->evaluateSilently ($ args [3 ]->value );
155+ if ($ thirdArgValue & JSON_OBJECT_AS_ARRAY ) {
156+ return true ;
157+ }
158+ }
128159 }
129160
130- return $ secondArgValue -> name -> toLowerString () === ' true ' ;
161+ return false ;
131162 }
132163
133164 private function resolveConstantStringType (ConstantStringType $ constantStringType , bool $ isForceArray ): Type
0 commit comments