From 1052da8d9f68017b3e6fe93fd57c10ccde993fc4 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Fri, 6 Nov 2026 19:34:23 +0700 Subject: [PATCH 1/3] [Php84] Skip variable modified in condition on ForeachToArrayAll/Any/Find/FindKeyRector --- .../skip_modify_outer_variable.php.inc | 46 +++++++++++++++++++ .../skip_modify_outer_variable.php.inc | 32 +++++++++++++ .../skip_modify_outer_variable.php.inc | 20 ++++++++ .../skip_modify_outer_variable.php.inc | 20 ++++++++ .../VariableModifiedInConditionalAnalyzer.php | 39 ++++++++++++++++ .../ForeachToArrayAnyAllFactory.php | 13 +++++- .../NodeFactory/ForeachToArrayFindFactory.php | 9 +++- 7 files changed, 177 insertions(+), 2 deletions(-) create mode 100644 rules-tests/Php84/Rector/Foreach_/ForeachToArrayAllRector/Fixture/skip_modify_outer_variable.php.inc create mode 100644 rules-tests/Php84/Rector/Foreach_/ForeachToArrayAnyRector/Fixture/skip_modify_outer_variable.php.inc create mode 100644 rules-tests/Php84/Rector/Foreach_/ForeachToArrayFindKeyRector/Fixture/skip_modify_outer_variable.php.inc create mode 100644 rules-tests/Php84/Rector/Foreach_/ForeachToArrayFindRector/Fixture/skip_modify_outer_variable.php.inc create mode 100644 rules/Php84/NodeAnalyzer/VariableModifiedInConditionalAnalyzer.php diff --git a/rules-tests/Php84/Rector/Foreach_/ForeachToArrayAllRector/Fixture/skip_modify_outer_variable.php.inc b/rules-tests/Php84/Rector/Foreach_/ForeachToArrayAllRector/Fixture/skip_modify_outer_variable.php.inc new file mode 100644 index 00000000000..157d7294a1a --- /dev/null +++ b/rules-tests/Php84/Rector/Foreach_/ForeachToArrayAllRector/Fixture/skip_modify_outer_variable.php.inc @@ -0,0 +1,46 @@ + $limit) { + $valid = false; + break; + } + } + + return $valid; + } + + public function earlyReturn(array $items, array $others): bool + { + $i = 0; + foreach ($items as $item) { + if ($item !== $others[$i++]) { + return false; + } + } + + return true; + } +} diff --git a/rules-tests/Php84/Rector/Foreach_/ForeachToArrayAnyRector/Fixture/skip_modify_outer_variable.php.inc b/rules-tests/Php84/Rector/Foreach_/ForeachToArrayAnyRector/Fixture/skip_modify_outer_variable.php.inc new file mode 100644 index 00000000000..f89b580d8e9 --- /dev/null +++ b/rules-tests/Php84/Rector/Foreach_/ForeachToArrayAnyRector/Fixture/skip_modify_outer_variable.php.inc @@ -0,0 +1,32 @@ + $item) { + if ($item === $others[$i++]) { + $found = $idx; + break; + } + } + + return $found; + } +} diff --git a/rules-tests/Php84/Rector/Foreach_/ForeachToArrayFindRector/Fixture/skip_modify_outer_variable.php.inc b/rules-tests/Php84/Rector/Foreach_/ForeachToArrayFindRector/Fixture/skip_modify_outer_variable.php.inc new file mode 100644 index 00000000000..c9d832c9485 --- /dev/null +++ b/rules-tests/Php84/Rector/Foreach_/ForeachToArrayFindRector/Fixture/skip_modify_outer_variable.php.inc @@ -0,0 +1,20 @@ +betterNodeFinder->hasInstancesOf($expr, [ + Assign::class, + AssignOp::class, + AssignRef::class, + PreInc::class, + PreDec::class, + PostInc::class, + PostDec::class, + ]); + } +} diff --git a/rules/Php84/NodeFactory/ForeachToArrayAnyAllFactory.php b/rules/Php84/NodeFactory/ForeachToArrayAnyAllFactory.php index bc5c2206beb..4bd2cbb6fc7 100644 --- a/rules/Php84/NodeFactory/ForeachToArrayAnyAllFactory.php +++ b/rules/Php84/NodeFactory/ForeachToArrayAnyAllFactory.php @@ -21,6 +21,7 @@ use Rector\NodeNameResolver\NodeNameResolver; use Rector\NodeTypeResolver\NodeTypeResolver; use Rector\Php84\NodeAnalyzer\ForeachKeyUsedInConditionalAnalyzer; +use Rector\Php84\NodeAnalyzer\VariableModifiedInConditionalAnalyzer; use Rector\PhpParser\Comparing\NodeComparator; use Rector\PhpParser\Node\NodeFactory; use Rector\PhpParser\Node\Value\ValueResolver; @@ -37,7 +38,8 @@ public function __construct( private NodeComparator $nodeComparator, private NodeTypeResolver $nodeTypeResolver, private NodeFactory $nodeFactory, - private NodeNameResolver $nodeNameResolver + private NodeNameResolver $nodeNameResolver, + private VariableModifiedInConditionalAnalyzer $variableModifiedInConditionalAnalyzer ) { } @@ -112,6 +114,11 @@ private function refactorBooleanAssignmentPattern( $firstNodeInsideForeach = $foreach->stmts[0]; $condition = $firstNodeInsideForeach->cond; + + if ($this->variableModifiedInConditionalAnalyzer->isModified($condition)) { + continue; + } + $valueParam = $foreach->valueVar; if (! $valueParam instanceof Variable) { @@ -190,6 +197,10 @@ private function refactorEarlyReturnPattern( $firstNodeInsideForeach = $foreach->stmts[0]; $condition = $firstNodeInsideForeach->cond; + if ($this->variableModifiedInConditionalAnalyzer->isModified($condition)) { + continue; + } + $params = []; if ($foreach->valueVar instanceof Variable) { diff --git a/rules/Php84/NodeFactory/ForeachToArrayFindFactory.php b/rules/Php84/NodeFactory/ForeachToArrayFindFactory.php index 25340c3d82c..b7d50de2b4b 100644 --- a/rules/Php84/NodeFactory/ForeachToArrayFindFactory.php +++ b/rules/Php84/NodeFactory/ForeachToArrayFindFactory.php @@ -18,6 +18,7 @@ use Rector\NodeNameResolver\NodeNameResolver; use Rector\NodeTypeResolver\NodeTypeResolver; use Rector\Php84\NodeAnalyzer\ForeachKeyUsedInConditionalAnalyzer; +use Rector\Php84\NodeAnalyzer\VariableModifiedInConditionalAnalyzer; use Rector\PhpParser\Comparing\NodeComparator; use Rector\PhpParser\Node\NodeFactory; use Rector\PhpParser\Node\Value\ValueResolver; @@ -34,7 +35,8 @@ public function __construct( private NodeComparator $nodeComparator, private NodeTypeResolver $nodeTypeResolver, private NodeFactory $nodeFactory, - private NodeNameResolver $nodeNameResolver + private NodeNameResolver $nodeNameResolver, + private VariableModifiedInConditionalAnalyzer $variableModifiedInConditionalAnalyzer ) { } @@ -90,6 +92,11 @@ public function createArrayFindAssign(Node $node, string $functionName, bool $co $firstNodeInsideForeach = $foreach->stmts[0]; $condition = $firstNodeInsideForeach->cond; + + if ($this->variableModifiedInConditionalAnalyzer->isModified($condition)) { + continue; + } + $valueParam = $foreach->valueVar; if (! $valueParam instanceof Variable) { From 4bc859850055167eb75849735c1402173444044b Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Fri, 6 Nov 2026 19:37:13 +0700 Subject: [PATCH 2/3] rename service --- ...Analyzer.php => ExprModifiedInConditionalAnalyzer.php} | 2 +- rules/Php84/NodeFactory/ForeachToArrayAnyAllFactory.php | 8 ++++---- rules/Php84/NodeFactory/ForeachToArrayFindFactory.php | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) rename rules/Php84/NodeAnalyzer/{VariableModifiedInConditionalAnalyzer.php => ExprModifiedInConditionalAnalyzer.php} (93%) diff --git a/rules/Php84/NodeAnalyzer/VariableModifiedInConditionalAnalyzer.php b/rules/Php84/NodeAnalyzer/ExprModifiedInConditionalAnalyzer.php similarity index 93% rename from rules/Php84/NodeAnalyzer/VariableModifiedInConditionalAnalyzer.php rename to rules/Php84/NodeAnalyzer/ExprModifiedInConditionalAnalyzer.php index 86c05b8ff50..e129c727b1d 100644 --- a/rules/Php84/NodeAnalyzer/VariableModifiedInConditionalAnalyzer.php +++ b/rules/Php84/NodeAnalyzer/ExprModifiedInConditionalAnalyzer.php @@ -17,7 +17,7 @@ /** * Arrow functions capture outer variables by value, so writes inside the conditional would be lost */ -final readonly class VariableModifiedInConditionalAnalyzer +final readonly class ExprModifiedInConditionalAnalyzer { public function __construct( private BetterNodeFinder $betterNodeFinder diff --git a/rules/Php84/NodeFactory/ForeachToArrayAnyAllFactory.php b/rules/Php84/NodeFactory/ForeachToArrayAnyAllFactory.php index 4bd2cbb6fc7..03df500a46e 100644 --- a/rules/Php84/NodeFactory/ForeachToArrayAnyAllFactory.php +++ b/rules/Php84/NodeFactory/ForeachToArrayAnyAllFactory.php @@ -20,8 +20,8 @@ use Rector\NodeManipulator\StmtsManipulator; use Rector\NodeNameResolver\NodeNameResolver; use Rector\NodeTypeResolver\NodeTypeResolver; +use Rector\Php84\NodeAnalyzer\ExprModifiedInConditionalAnalyzer; use Rector\Php84\NodeAnalyzer\ForeachKeyUsedInConditionalAnalyzer; -use Rector\Php84\NodeAnalyzer\VariableModifiedInConditionalAnalyzer; use Rector\PhpParser\Comparing\NodeComparator; use Rector\PhpParser\Node\NodeFactory; use Rector\PhpParser\Node\Value\ValueResolver; @@ -39,7 +39,7 @@ public function __construct( private NodeTypeResolver $nodeTypeResolver, private NodeFactory $nodeFactory, private NodeNameResolver $nodeNameResolver, - private VariableModifiedInConditionalAnalyzer $variableModifiedInConditionalAnalyzer + private ExprModifiedInConditionalAnalyzer $exprModifiedInConditionalAnalyzer ) { } @@ -115,7 +115,7 @@ private function refactorBooleanAssignmentPattern( $condition = $firstNodeInsideForeach->cond; - if ($this->variableModifiedInConditionalAnalyzer->isModified($condition)) { + if ($this->exprModifiedInConditionalAnalyzer->isModified($condition)) { continue; } @@ -197,7 +197,7 @@ private function refactorEarlyReturnPattern( $firstNodeInsideForeach = $foreach->stmts[0]; $condition = $firstNodeInsideForeach->cond; - if ($this->variableModifiedInConditionalAnalyzer->isModified($condition)) { + if ($this->exprModifiedInConditionalAnalyzer->isModified($condition)) { continue; } diff --git a/rules/Php84/NodeFactory/ForeachToArrayFindFactory.php b/rules/Php84/NodeFactory/ForeachToArrayFindFactory.php index b7d50de2b4b..d7596023b58 100644 --- a/rules/Php84/NodeFactory/ForeachToArrayFindFactory.php +++ b/rules/Php84/NodeFactory/ForeachToArrayFindFactory.php @@ -17,8 +17,8 @@ use Rector\NodeManipulator\StmtsManipulator; use Rector\NodeNameResolver\NodeNameResolver; use Rector\NodeTypeResolver\NodeTypeResolver; +use Rector\Php84\NodeAnalyzer\ExprModifiedInConditionalAnalyzer; use Rector\Php84\NodeAnalyzer\ForeachKeyUsedInConditionalAnalyzer; -use Rector\Php84\NodeAnalyzer\VariableModifiedInConditionalAnalyzer; use Rector\PhpParser\Comparing\NodeComparator; use Rector\PhpParser\Node\NodeFactory; use Rector\PhpParser\Node\Value\ValueResolver; @@ -36,7 +36,7 @@ public function __construct( private NodeTypeResolver $nodeTypeResolver, private NodeFactory $nodeFactory, private NodeNameResolver $nodeNameResolver, - private VariableModifiedInConditionalAnalyzer $variableModifiedInConditionalAnalyzer + private ExprModifiedInConditionalAnalyzer $exprModifiedInConditionalAnalyzer ) { } @@ -93,7 +93,7 @@ public function createArrayFindAssign(Node $node, string $functionName, bool $co $condition = $firstNodeInsideForeach->cond; - if ($this->variableModifiedInConditionalAnalyzer->isModified($condition)) { + if ($this->exprModifiedInConditionalAnalyzer->isModified($condition)) { continue; } From 8c7bbc62f8b46df6ef9969064592c8ca5b75efdc Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Fri, 6 Nov 2026 19:39:32 +0700 Subject: [PATCH 3/3] fix --- .../NodeFactory/ForeachToArrayAnyAllFactory.php | 17 ++++++++--------- .../NodeFactory/ForeachToArrayFindFactory.php | 9 ++++----- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/rules/Php84/NodeFactory/ForeachToArrayAnyAllFactory.php b/rules/Php84/NodeFactory/ForeachToArrayAnyAllFactory.php index 03df500a46e..33f1e1fdb3a 100644 --- a/rules/Php84/NodeFactory/ForeachToArrayAnyAllFactory.php +++ b/rules/Php84/NodeFactory/ForeachToArrayAnyAllFactory.php @@ -114,11 +114,6 @@ private function refactorBooleanAssignmentPattern( $firstNodeInsideForeach = $foreach->stmts[0]; $condition = $firstNodeInsideForeach->cond; - - if ($this->exprModifiedInConditionalAnalyzer->isModified($condition)) { - continue; - } - $valueParam = $foreach->valueVar; if (! $valueParam instanceof Variable) { @@ -197,10 +192,6 @@ private function refactorEarlyReturnPattern( $firstNodeInsideForeach = $foreach->stmts[0]; $condition = $firstNodeInsideForeach->cond; - if ($this->exprModifiedInConditionalAnalyzer->isModified($condition)) { - continue; - } - $params = []; if ($foreach->valueVar instanceof Variable) { @@ -249,6 +240,10 @@ private function isValidBooleanAssignmentForeachStructure( return false; } + if ($this->exprModifiedInConditionalAnalyzer->isModified($firstStmt->cond)) { + return false; + } + if ($rejectElseBranches && ($firstStmt->elseifs !== [] || $firstStmt->else instanceof Else_)) { return false; } @@ -294,6 +289,10 @@ private function isValidEarlyReturnForeachStructure( $ifStmt = $foreach->stmts[0]; + if ($this->exprModifiedInConditionalAnalyzer->isModified($ifStmt->cond)) { + return false; + } + if ($rejectElseBranches && ($ifStmt->elseifs !== [] || $ifStmt->else instanceof Else_)) { return false; } diff --git a/rules/Php84/NodeFactory/ForeachToArrayFindFactory.php b/rules/Php84/NodeFactory/ForeachToArrayFindFactory.php index d7596023b58..ba3dc5bb69a 100644 --- a/rules/Php84/NodeFactory/ForeachToArrayFindFactory.php +++ b/rules/Php84/NodeFactory/ForeachToArrayFindFactory.php @@ -92,11 +92,6 @@ public function createArrayFindAssign(Node $node, string $functionName, bool $co $firstNodeInsideForeach = $foreach->stmts[0]; $condition = $firstNodeInsideForeach->cond; - - if ($this->exprModifiedInConditionalAnalyzer->isModified($condition)) { - continue; - } - $valueParam = $foreach->valueVar; if (! $valueParam instanceof Variable) { @@ -152,6 +147,10 @@ private function isValidForeachStructure(Foreach_ $foreach, Variable $assignedVa return false; } + if ($this->exprModifiedInConditionalAnalyzer->isModified($firstStmt->cond)) { + return false; + } + $assignmentStmt = $firstStmt->stmts[0]; $breakStmt = $firstStmt->stmts[1];