From 96e4b21faaec64dd8241eb4641c587c861d141ef Mon Sep 17 00:00:00 2001 From: lenny4 Date: Fri, 30 Jan 2026 02:13:03 +0100 Subject: [PATCH 1/7] add JSON_THROW_ON_ERROR even if there is already a flag --- .../Fixture/fixture.php.inc | 4 + .../FuncCall/JsonThrowOnErrorRector.php | 80 ++++++++++++++++--- 2 files changed, 75 insertions(+), 9 deletions(-) diff --git a/rules-tests/Php73/Rector/FuncCall/JsonThrowOnErrorRector/Fixture/fixture.php.inc b/rules-tests/Php73/Rector/FuncCall/JsonThrowOnErrorRector/Fixture/fixture.php.inc index e47df536fe3..4447810e978 100644 --- a/rules-tests/Php73/Rector/FuncCall/JsonThrowOnErrorRector/Fixture/fixture.php.inc +++ b/rules-tests/Php73/Rector/FuncCall/JsonThrowOnErrorRector/Fixture/fixture.php.inc @@ -10,6 +10,8 @@ function jsonThrowOnError() json_decode($json, true, 215); json_decode($json, true, 122, JSON_THROW_ON_ERROR); + + json_decode($json, true, 122, JSON_UNESCAPED_UNICODE); } ?> @@ -26,6 +28,8 @@ function jsonThrowOnError() json_decode($json, true, 215, JSON_THROW_ON_ERROR); json_decode($json, true, 122, JSON_THROW_ON_ERROR); + + json_decode($json, true, 122, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR); } ?> diff --git a/rules/Php73/Rector/FuncCall/JsonThrowOnErrorRector.php b/rules/Php73/Rector/FuncCall/JsonThrowOnErrorRector.php index d79e97d8d57..463050dcffe 100644 --- a/rules/Php73/Rector/FuncCall/JsonThrowOnErrorRector.php +++ b/rules/Php73/Rector/FuncCall/JsonThrowOnErrorRector.php @@ -26,6 +26,7 @@ final class JsonThrowOnErrorRector extends AbstractRector implements MinPhpVersionInterface { private bool $hasChanged = false; + private const FLAGS = ['JSON_THROW_ON_ERROR']; public function __construct( private readonly ValueResolver $valueResolver, @@ -135,20 +136,22 @@ private function shouldSkipFuncCall(FuncCall $funcCall): bool private function processJsonEncode(FuncCall $funcCall): ?FuncCall { + $flags = []; if (isset($funcCall->args[1])) { - return null; + $flags = $this->getFlags($funcCall->args[1]); + } + if (!is_null($newArg = $this->getArgWithFlags($flags))) { + $this->hasChanged = true; + $funcCall->args[1] = $newArg; } - - $this->hasChanged = true; - - $funcCall->args[1] = new Arg($this->createConstFetch('JSON_THROW_ON_ERROR')); return $funcCall; } private function processJsonDecode(FuncCall $funcCall): ?FuncCall { + $flags = []; if (isset($funcCall->args[3])) { - return null; + $flags = $this->getFlags($funcCall->args[3]); } // set default to inter-args @@ -160,9 +163,10 @@ private function processJsonDecode(FuncCall $funcCall): ?FuncCall $funcCall->args[2] = new Arg(new Int_(512)); } - $this->hasChanged = true; - $funcCall->args[3] = new Arg($this->createConstFetch('JSON_THROW_ON_ERROR')); - + if (!is_null($newArg = $this->getArgWithFlags($flags))) { + $this->hasChanged = true; + $funcCall->args[3] = $newArg; + } return $funcCall; } @@ -186,4 +190,62 @@ private function isFirstValueStringOrArray(FuncCall $funcCall): bool return is_array($value); } + + private function getFlags(Arg|Node\Expr\BinaryOp\BitwiseOr|ConstFetch $arg, array $result = []): array + { + if ($arg instanceof ConstFetch) { + $constFetch = $arg; + } else { + if ($arg instanceof Arg) { + $array = $arg->value->jsonSerialize(); + } else { + $array = $arg->jsonSerialize(); + } + if ($arg->value instanceof ConstFetch) { // single flag + $constFetch = $arg->value; + } else { // multiple flag + $result = $this->getFlags($array['left'], $result); + $constFetch = $array['right']; + } + } + if (!is_null($constFetch)) { + $result[] = $constFetch->jsonSerialize()['name']->getFirst(); + } + return $result; + } + + private function getArgWithFlags(array $flags): Arg|null + { + $oldNbFlags = count($flags); + $flags = array_values(array_unique([...$flags, ...self::FLAGS])); + $newNbFlags = count($flags); + if ($oldNbFlags === $newNbFlags) { + return null; + } + if ($newNbFlags === 1) { + return new Arg($this->createConstFetch($flags[0])); + } + $constFetchs = []; + foreach ($flags as $flag) { + $constFetchs[] = $this->createConstFetch($flag); + } + $result = null; + foreach ($constFetchs as $i => $constFetch) { + if ($i === 1) { + continue; + } + if (is_null($result)) { + $result = new Node\Expr\BinaryOp\BitwiseOr( + $constFetch, + $constFetchs[$i + 1], + ); + } else { + $result = new Node\Expr\BinaryOp\BitwiseOr( + $result, + $constFetch + ); + } + } + return new Arg($result); + } } From 3d0469963d0fafd311e0c5dc9797ad1bae81fe94 Mon Sep 17 00:00:00 2001 From: lenny4 Date: Fri, 30 Jan 2026 02:17:19 +0100 Subject: [PATCH 2/7] rename $constFetchs to $constFetches --- rules/Php73/Rector/FuncCall/JsonThrowOnErrorRector.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rules/Php73/Rector/FuncCall/JsonThrowOnErrorRector.php b/rules/Php73/Rector/FuncCall/JsonThrowOnErrorRector.php index 463050dcffe..c6d53a1640a 100644 --- a/rules/Php73/Rector/FuncCall/JsonThrowOnErrorRector.php +++ b/rules/Php73/Rector/FuncCall/JsonThrowOnErrorRector.php @@ -225,19 +225,19 @@ private function getArgWithFlags(array $flags): Arg|null if ($newNbFlags === 1) { return new Arg($this->createConstFetch($flags[0])); } - $constFetchs = []; + $constFetches = []; foreach ($flags as $flag) { - $constFetchs[] = $this->createConstFetch($flag); + $constFetches[] = $this->createConstFetch($flag); } $result = null; - foreach ($constFetchs as $i => $constFetch) { + foreach ($constFetches as $i => $constFetch) { if ($i === 1) { continue; } if (is_null($result)) { $result = new Node\Expr\BinaryOp\BitwiseOr( $constFetch, - $constFetchs[$i + 1], + $constFetches[$i + 1], ); } else { $result = new Node\Expr\BinaryOp\BitwiseOr( From 30782800ea2fbc00f5ef3f2557c1223663bde355 Mon Sep 17 00:00:00 2001 From: lenny4 Date: Fri, 30 Jan 2026 02:52:09 +0100 Subject: [PATCH 3/7] fix phpstan --- .../FuncCall/JsonThrowOnErrorRector.php | 42 +++++++++++++------ 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/rules/Php73/Rector/FuncCall/JsonThrowOnErrorRector.php b/rules/Php73/Rector/FuncCall/JsonThrowOnErrorRector.php index c6d53a1640a..ae2e3cdd7ce 100644 --- a/rules/Php73/Rector/FuncCall/JsonThrowOnErrorRector.php +++ b/rules/Php73/Rector/FuncCall/JsonThrowOnErrorRector.php @@ -26,7 +26,7 @@ final class JsonThrowOnErrorRector extends AbstractRector implements MinPhpVersionInterface { private bool $hasChanged = false; - private const FLAGS = ['JSON_THROW_ON_ERROR']; + private const array FLAGS = ['JSON_THROW_ON_ERROR']; public function __construct( private readonly ValueResolver $valueResolver, @@ -62,9 +62,6 @@ public function getNodeTypes(): array return NodeGroup::STMTS_AWARE; } - /** - * @param StmtsAware $node - */ public function refactor(Node $node): ?Node { // if found, skip it :) @@ -134,11 +131,13 @@ private function shouldSkipFuncCall(FuncCall $funcCall): bool return $this->isFirstValueStringOrArray($funcCall); } - private function processJsonEncode(FuncCall $funcCall): ?FuncCall + private function processJsonEncode(FuncCall $funcCall): FuncCall { $flags = []; if (isset($funcCall->args[1])) { - $flags = $this->getFlags($funcCall->args[1]); + /** @var Arg|Node\Expr\BinaryOp\BitwiseOr|ConstFetch $arg */ + $arg = $funcCall->args[1]; + $flags = $this->getFlags($arg); } if (!is_null($newArg = $this->getArgWithFlags($flags))) { $this->hasChanged = true; @@ -147,11 +146,13 @@ private function processJsonEncode(FuncCall $funcCall): ?FuncCall return $funcCall; } - private function processJsonDecode(FuncCall $funcCall): ?FuncCall + private function processJsonDecode(FuncCall $funcCall): FuncCall { $flags = []; if (isset($funcCall->args[3])) { - $flags = $this->getFlags($funcCall->args[3]); + /** @var Arg|Node\Expr\BinaryOp\BitwiseOr|ConstFetch $arg */ + $arg = $funcCall->args[3]; + $flags = $this->getFlags($arg); } // set default to inter-args @@ -191,7 +192,11 @@ private function isFirstValueStringOrArray(FuncCall $funcCall): bool return is_array($value); } - private function getFlags(Arg|Node\Expr\BinaryOp\BitwiseOr|ConstFetch $arg, array $result = []): array + /** + * @param string[] $flags + * @return string[] + */ + private function getFlags(Arg|Node\Expr\BinaryOp\BitwiseOr|ConstFetch $arg, array $flags = []): array { if ($arg instanceof ConstFetch) { $constFetch = $arg; @@ -201,19 +206,27 @@ private function getFlags(Arg|Node\Expr\BinaryOp\BitwiseOr|ConstFetch $arg, arra } else { $array = $arg->jsonSerialize(); } - if ($arg->value instanceof ConstFetch) { // single flag + if ($arg instanceof Arg && $arg->value instanceof ConstFetch) { // single flag $constFetch = $arg->value; } else { // multiple flag - $result = $this->getFlags($array['left'], $result); + $flags = $this->getFlags($array['left'], $flags); $constFetch = $array['right']; } } if (!is_null($constFetch)) { - $result[] = $constFetch->jsonSerialize()['name']->getFirst(); + /** @var ConstFetch $constFetch */ + $json = $constFetch->jsonSerialize(); + if (isset($json['name']) && $json['name'] instanceof Name) { + $name = $json['name']; + $flags[] = $name->getFirst(); + } } - return $result; + return $flags; } + /** + * @param string[] $flags + */ private function getArgWithFlags(array $flags): Arg|null { $oldNbFlags = count($flags); @@ -246,6 +259,9 @@ private function getArgWithFlags(array $flags): Arg|null ); } } + if (is_null($result)) { + return null; + } return new Arg($result); } } From d942f0deea9aaff872e71a2e3a0cd04808d986ce Mon Sep 17 00:00:00 2001 From: lenny4 Date: Fri, 30 Jan 2026 15:41:25 +0100 Subject: [PATCH 4/7] improve lisibility getFlags --- .../FuncCall/JsonThrowOnErrorRector.php | 36 ++++++++----------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/rules/Php73/Rector/FuncCall/JsonThrowOnErrorRector.php b/rules/Php73/Rector/FuncCall/JsonThrowOnErrorRector.php index ae2e3cdd7ce..026c01b8abe 100644 --- a/rules/Php73/Rector/FuncCall/JsonThrowOnErrorRector.php +++ b/rules/Php73/Rector/FuncCall/JsonThrowOnErrorRector.php @@ -6,6 +6,7 @@ use PhpParser\Node; use PhpParser\Node\Arg; +use PhpParser\Node\Expr; use PhpParser\Node\Expr\ConstFetch; use PhpParser\Node\Expr\FuncCall; use PhpParser\Node\Identifier; @@ -196,30 +197,23 @@ private function isFirstValueStringOrArray(FuncCall $funcCall): bool * @param string[] $flags * @return string[] */ - private function getFlags(Arg|Node\Expr\BinaryOp\BitwiseOr|ConstFetch $arg, array $flags = []): array + private function getFlags(Expr|Arg $arg, array $flags = []): array { + // Unwrap Arg + if ($arg instanceof Arg) { + $arg = $arg->value; + } + + // Single flag: SOME_CONST if ($arg instanceof ConstFetch) { - $constFetch = $arg; - } else { - if ($arg instanceof Arg) { - $array = $arg->value->jsonSerialize(); - } else { - $array = $arg->jsonSerialize(); - } - if ($arg instanceof Arg && $arg->value instanceof ConstFetch) { // single flag - $constFetch = $arg->value; - } else { // multiple flag - $flags = $this->getFlags($array['left'], $flags); - $constFetch = $array['right']; - } + $flags[] = $arg->name->getFirst(); + return $flags; } - if (!is_null($constFetch)) { - /** @var ConstFetch $constFetch */ - $json = $constFetch->jsonSerialize(); - if (isset($json['name']) && $json['name'] instanceof Name) { - $name = $json['name']; - $flags[] = $name->getFirst(); - } + + // Multiple flags: FLAG_A | FLAG_B | FLAG_C + if ($arg instanceof Node\Expr\BinaryOp\BitwiseOr) { + $flags = $this->getFlags($arg->left, $flags); + $flags = $this->getFlags($arg->right, $flags); } return $flags; } From f4dd08dfaf1adcac8da2331a2b3e0c7553ac26c5 Mon Sep 17 00:00:00 2001 From: lenny4 Date: Fri, 30 Jan 2026 15:47:31 +0100 Subject: [PATCH 5/7] improve lisibility getArgWithFlags --- .../FuncCall/JsonThrowOnErrorRector.php | 44 +++++++------------ 1 file changed, 15 insertions(+), 29 deletions(-) diff --git a/rules/Php73/Rector/FuncCall/JsonThrowOnErrorRector.php b/rules/Php73/Rector/FuncCall/JsonThrowOnErrorRector.php index 026c01b8abe..f1ec83345fc 100644 --- a/rules/Php73/Rector/FuncCall/JsonThrowOnErrorRector.php +++ b/rules/Php73/Rector/FuncCall/JsonThrowOnErrorRector.php @@ -221,41 +221,27 @@ private function getFlags(Expr|Arg $arg, array $flags = []): array /** * @param string[] $flags */ - private function getArgWithFlags(array $flags): Arg|null + private function getArgWithFlags(array $flags): ?Arg { - $oldNbFlags = count($flags); - $flags = array_values(array_unique([...$flags, ...self::FLAGS])); - $newNbFlags = count($flags); - if ($oldNbFlags === $newNbFlags) { + $originalCount = count($flags); + $flags = array_values(array_unique(array_merge($flags, self::FLAGS))); + if ($originalCount === count($flags)) { return null; } - if ($newNbFlags === 1) { + // Single flag + if (count($flags) === 1) { return new Arg($this->createConstFetch($flags[0])); } - $constFetches = []; + // Build FLAG_A | FLAG_B | FLAG_C + $expr = $this->createConstFetch(array_shift($flags)); + foreach ($flags as $flag) { - $constFetches[] = $this->createConstFetch($flag); - } - $result = null; - foreach ($constFetches as $i => $constFetch) { - if ($i === 1) { - continue; - } - if (is_null($result)) { - $result = new Node\Expr\BinaryOp\BitwiseOr( - $constFetch, - $constFetches[$i + 1], - ); - } else { - $result = new Node\Expr\BinaryOp\BitwiseOr( - $result, - $constFetch - ); - } - } - if (is_null($result)) { - return null; + $expr = new Node\Expr\BinaryOp\BitwiseOr( + $expr, + $this->createConstFetch($flag) + ); } - return new Arg($result); + + return new Arg($expr); } } From 555170a475073178cc2447e0bf990254f516a16f Mon Sep 17 00:00:00 2001 From: lenny4 Date: Fri, 30 Jan 2026 15:54:08 +0100 Subject: [PATCH 6/7] handle case the same flag is written multiple times --- rules/Php73/Rector/FuncCall/JsonThrowOnErrorRector.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rules/Php73/Rector/FuncCall/JsonThrowOnErrorRector.php b/rules/Php73/Rector/FuncCall/JsonThrowOnErrorRector.php index f1ec83345fc..a1374a58b42 100644 --- a/rules/Php73/Rector/FuncCall/JsonThrowOnErrorRector.php +++ b/rules/Php73/Rector/FuncCall/JsonThrowOnErrorRector.php @@ -215,7 +215,7 @@ private function getFlags(Expr|Arg $arg, array $flags = []): array $flags = $this->getFlags($arg->left, $flags); $flags = $this->getFlags($arg->right, $flags); } - return $flags; + return array_values(array_unique($flags)); // array_unique is case the same flag is written multiple times } /** From 594cb4d9e2b1f629f32023e816883065f993e347 Mon Sep 17 00:00:00 2001 From: lenny4 Date: Fri, 30 Jan 2026 16:11:31 +0100 Subject: [PATCH 7/7] please move $newArg = $this->getArgWithFlags($flags) to before if, and use ! instanceof check instead of ! is_null --- rules/Php73/Rector/FuncCall/JsonThrowOnErrorRector.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/rules/Php73/Rector/FuncCall/JsonThrowOnErrorRector.php b/rules/Php73/Rector/FuncCall/JsonThrowOnErrorRector.php index a1374a58b42..c8bab108b0e 100644 --- a/rules/Php73/Rector/FuncCall/JsonThrowOnErrorRector.php +++ b/rules/Php73/Rector/FuncCall/JsonThrowOnErrorRector.php @@ -140,7 +140,8 @@ private function processJsonEncode(FuncCall $funcCall): FuncCall $arg = $funcCall->args[1]; $flags = $this->getFlags($arg); } - if (!is_null($newArg = $this->getArgWithFlags($flags))) { + $newArg = $this->getArgWithFlags($flags); + if ($newArg instanceof Arg) { $this->hasChanged = true; $funcCall->args[1] = $newArg; } @@ -165,7 +166,8 @@ private function processJsonDecode(FuncCall $funcCall): FuncCall $funcCall->args[2] = new Arg(new Int_(512)); } - if (!is_null($newArg = $this->getArgWithFlags($flags))) { + $newArg = $this->getArgWithFlags($flags); + if ($newArg instanceof Arg) { $this->hasChanged = true; $funcCall->args[3] = $newArg; }