Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

?>
Expand All @@ -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);
}

?>
88 changes: 74 additions & 14 deletions rules/Php73/Rector/FuncCall/JsonThrowOnErrorRector.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -26,6 +27,7 @@
final class JsonThrowOnErrorRector extends AbstractRector implements MinPhpVersionInterface
{
private bool $hasChanged = false;
private const array FLAGS = ['JSON_THROW_ON_ERROR'];

public function __construct(
private readonly ValueResolver $valueResolver,
Expand Down Expand Up @@ -61,9 +63,6 @@ public function getNodeTypes(): array
return NodeGroup::STMTS_AWARE;
}

/**
* @param StmtsAware $node
*/
public function refactor(Node $node): ?Node
{
// if found, skip it :)
Expand Down Expand Up @@ -133,22 +132,29 @@ 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])) {
return null;
/** @var Arg $arg */
$arg = $funcCall->args[1];
$flags = $this->getFlags($arg);
}
$newArg = $this->getArgWithFlags($flags);
if ($newArg instanceof Arg) {
$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
private function processJsonDecode(FuncCall $funcCall): FuncCall
{
$flags = [];
if (isset($funcCall->args[3])) {
return null;
/** @var Arg $arg */
$arg = $funcCall->args[3];
$flags = $this->getFlags($arg);
}

// set default to inter-args
Expand All @@ -160,9 +166,11 @@ 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'));

$newArg = $this->getArgWithFlags($flags);
if ($newArg instanceof Arg) {
$this->hasChanged = true;
$funcCall->args[3] = $newArg;
}
return $funcCall;
}

Expand All @@ -186,4 +194,56 @@ private function isFirstValueStringOrArray(FuncCall $funcCall): bool

return is_array($value);
}

/**
* @param string[] $flags
* @return string[]
*/
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) {
$flags[] = $arg->name->getFirst();
return $flags;
}

// 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 array_values(array_unique($flags)); // array_unique in case the same flag is written multiple times
}

/**
* @param string[] $flags
*/
private function getArgWithFlags(array $flags): ?Arg
{
$originalCount = count($flags);
$flags = array_values(array_unique(array_merge($flags, self::FLAGS)));
if ($originalCount === count($flags)) {
return null;
}
// Single flag
if (count($flags) === 1) {
return new Arg($this->createConstFetch($flags[0]));
}
// Build FLAG_A | FLAG_B | FLAG_C
$expr = $this->createConstFetch(array_shift($flags));

foreach ($flags as $flag) {
$expr = new Node\Expr\BinaryOp\BitwiseOr(
$expr,
$this->createConstFetch($flag)
);
}

return new Arg($expr);
}
}