No Update Loop Vars

ID

php.no_update_loop_vars

Severity

low

Remediation Complexity

trivial

Remediation Risk

low

Remediation Effort

low

Resource

Reliability

Language

Php

Tags

control-flow, reliability

Description

Reports a for loop whose control variable — the variable assigned or stepped in the loop header — is also modified inside the loop body. The rule covers every mutation form: plain assignment ($i = …​), compound assignment ($i += …​), post-increment/decrement ($i`, `$i--`) and pre-increment/decrement (`$i, --$i).

Rationale

A for loop reads as a self-contained contract: the header declares how the control variable starts, when the loop ends, and how it advances each pass. When the body also changes that variable, the real iteration count no longer matches the header, and the loop becomes hard to follow. The result is frequently an off-by-one error, a skipped element, or an unintended infinite loop. Keeping all control-variable updates in the header restores the single, obvious advance step.

<?php
function process(array $items): void
{
    for ($i = 0; $i < count($items); $i++) {
        echo $items[$i];
        $i++;                 // FLAW - the body advances the counter a second time
    }

    for ($j = 0; $j < count($items); $j++) {
        echo $items[$j];      // OK - the control variable is only read in the body
    }
}

Remediation

Decide where the control variable should advance and keep it in one place. If two steps per pass are intended, encode that in the header (for example $i += 2). If the extra update was accidental, remove it from the body. When the body genuinely needs to skip ahead based on data, prefer a while loop so the manual advance is explicit rather than hidden alongside a header that says otherwise.

References