From bf42256b0c9d9276569dd79d031975c930c3c2ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6ren=20W=C3=BCnsch?= Date: Fri, 13 Feb 2026 13:35:09 +0100 Subject: [PATCH 1/2] feat: add support for converting is_null ternaries to null coalescing operator --- .../Fixture/is_null_function.php.inc | 34 +++++++++++++++ .../Ternary/TernaryToNullCoalescingRector.php | 41 +++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 rules-tests/Php70/Rector/Ternary/TernaryToNullCoalescingRector/Fixture/is_null_function.php.inc diff --git a/rules-tests/Php70/Rector/Ternary/TernaryToNullCoalescingRector/Fixture/is_null_function.php.inc b/rules-tests/Php70/Rector/Ternary/TernaryToNullCoalescingRector/Fixture/is_null_function.php.inc new file mode 100644 index 00000000000..c7ea08e38af --- /dev/null +++ b/rules-tests/Php70/Rector/Ternary/TernaryToNullCoalescingRector/Fixture/is_null_function.php.inc @@ -0,0 +1,34 @@ + +----- + + diff --git a/rules/Php70/Rector/Ternary/TernaryToNullCoalescingRector.php b/rules/Php70/Rector/Ternary/TernaryToNullCoalescingRector.php index 2590a75baf2..7de771559eb 100644 --- a/rules/Php70/Rector/Ternary/TernaryToNullCoalescingRector.php +++ b/rules/Php70/Rector/Ternary/TernaryToNullCoalescingRector.php @@ -9,6 +9,8 @@ use PhpParser\Node\Expr\BinaryOp\Coalesce; use PhpParser\Node\Expr\BinaryOp\Identical; use PhpParser\Node\Expr\BinaryOp\NotIdentical; +use PhpParser\Node\Expr\BooleanNot; +use PhpParser\Node\Expr\FuncCall; use PhpParser\Node\Expr\Isset_; use PhpParser\Node\Expr\Ternary; use Rector\PhpParser\Node\Value\ValueResolver; @@ -35,6 +37,7 @@ public function getRuleDefinition(): RuleDefinition [ new CodeSample('$value === null ? 10 : $value;', '$value ?? 10;'), new CodeSample('isset($value) ? $value : 10;', '$value ?? 10;'), + new CodeSample('is_null($value) ? 10 : $value;', '$value ?? 10;'), ] ); } @@ -56,6 +59,17 @@ public function refactor(Node $node): ?Node return $this->processTernaryWithIsset($node, $node->cond); } + if ($node->cond instanceof FuncCall && $this->isName($node->cond, 'is_null')) { + return $this->processTernaryWithIsNull($node, $node->cond, false); + } + + if ( + $node->cond instanceof BooleanNot && $node->cond->expr instanceof FuncCall + && $this->isName($node->cond->expr, 'is_null') + ) { + return $this->processTernaryWithIsNull($node, $node->cond->expr, true); + } + if ($node->cond instanceof Identical) { $checkedNode = $node->else; $fallbackNode = $node->if; @@ -93,6 +107,33 @@ public function provideMinPhpVersion(): int return PhpVersionFeature::NULL_COALESCE; } + private function processTernaryWithIsNull(Ternary $ternary, FuncCall $isNullFuncCall, bool $isNegated): ?Coalesce + { + if (count($isNullFuncCall->args) !== 1) { + return null; + } + + $checkedExpr = $isNullFuncCall->args[0]->value; + + if ($isNegated) { + if (! $ternary->if instanceof Expr) { + return null; + } + + if (! $this->nodeComparator->areNodesEqual($ternary->if, $checkedExpr)) { + return null; + } + + return new Coalesce($ternary->if, $ternary->else); + } + + if (! $this->nodeComparator->areNodesEqual($ternary->else, $checkedExpr)) { + return null; + } + + return new Coalesce($ternary->else, $ternary->if); + } + private function processTernaryWithIsset(Ternary $ternary, Isset_ $isset): ?Coalesce { if (! $ternary->if instanceof Expr) { From 9477ba27b15f8894f249595e02fd7ee9b6b98271 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6ren=20W=C3=BCnsch?= Date: Fri, 13 Feb 2026 13:49:08 +0100 Subject: [PATCH 2/2] Fix errors --- .../Fixture/is_null_function.php.inc | 1 - .../Rector/Ternary/TernaryToNullCoalescingRector.php | 11 ++++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/rules-tests/Php70/Rector/Ternary/TernaryToNullCoalescingRector/Fixture/is_null_function.php.inc b/rules-tests/Php70/Rector/Ternary/TernaryToNullCoalescingRector/Fixture/is_null_function.php.inc index c7ea08e38af..9e5a19156e2 100644 --- a/rules-tests/Php70/Rector/Ternary/TernaryToNullCoalescingRector/Fixture/is_null_function.php.inc +++ b/rules-tests/Php70/Rector/Ternary/TernaryToNullCoalescingRector/Fixture/is_null_function.php.inc @@ -31,4 +31,3 @@ function ternaryWithIsNull() } ?> - diff --git a/rules/Php70/Rector/Ternary/TernaryToNullCoalescingRector.php b/rules/Php70/Rector/Ternary/TernaryToNullCoalescingRector.php index 7de771559eb..24833334691 100644 --- a/rules/Php70/Rector/Ternary/TernaryToNullCoalescingRector.php +++ b/rules/Php70/Rector/Ternary/TernaryToNullCoalescingRector.php @@ -113,7 +113,12 @@ private function processTernaryWithIsNull(Ternary $ternary, FuncCall $isNullFunc return null; } - $checkedExpr = $isNullFuncCall->args[0]->value; + $firstArg = $isNullFuncCall->args[0]; + if (! $firstArg instanceof Node\Arg) { + return null; + } + + $checkedExpr = $firstArg->value; if ($isNegated) { if (! $ternary->if instanceof Expr) { @@ -131,6 +136,10 @@ private function processTernaryWithIsNull(Ternary $ternary, FuncCall $isNullFunc return null; } + if (! $ternary->if instanceof Expr) { + return null; + } + return new Coalesce($ternary->else, $ternary->if); }