Skip to content

Commit b2b979b

Browse files
TomasVotrubaondrejmirtes
authored andcommitted
check for JSON_OBJECT_AS_ARRAY, in case of null and array
1 parent d665722 commit b2b979b

File tree

2 files changed

+49
-12
lines changed

2 files changed

+49
-12
lines changed

src/Type/Php/JsonThrowOnErrorDynamicReturnTypeExtension.php

Lines changed: 43 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,13 @@
22

33
namespace PHPStan\Type\Php;
44

5+
<<<<<<< HEAD
56
<<<<<<< HEAD
67
=======
78
use PhpParser\Node\Arg;
9+
=======
10+
use PhpParser\ConstExprEvaluator;
11+
>>>>>>> check for JSON_OBJECT_AS_ARRAY, in case of null and array
812
use PhpParser\Node\Expr;
913
use PhpParser\Node\Expr\BinaryOp\BitwiseOr;
1014
use PhpParser\Node\Expr\ConstFetch;
@@ -35,7 +39,9 @@
3539
use PHPStan\Type\Type;
3640
use PHPStan\Type\TypeCombinator;
3741
use stdClass;
42+
use function constant;
3843
use function json_decode;
44+
use const JSON_OBJECT_AS_ARRAY;
3945

4046
class 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

tests/PHPStan/Analyser/data/json-decode/narrow_type_with_force_array.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,9 @@ function ($mixed) {
2626
$value = json_decode($mixed, true);
2727
assertType('mixed~stdClass', $value);
2828
};
29+
30+
// @see https://3v4l.org/YFlHF
31+
function ($mixed) {
32+
$value = json_decode($mixed, null, 512, JSON_OBJECT_AS_ARRAY);
33+
assertType('mixed~stdClass', $value);
34+
};

0 commit comments

Comments
 (0)