The Loop That Changed Only the Last Row
A by-reference foreach leaves a dangling reference that corrupts the total
Published Oct 29, 2026
Executive Summary
A reporting script applies VAT with a by-reference foreach and forgets unset($item). The next loop reuses $item, which still points at the last array element, so every iteration overwrites it. The final totals are wrong, and the bug is invisible for small arrays.
The Prompt
“Write a script that applies 20% VAT to each line item and then prints the totals per category.”
The Flaw
After the first loop, $item is still a reference to the last element. When the second foreach reuses the same variable, every iteration writes the current element into that referenced slot — so the last element ends up holding the second-to-last element's value (240 instead of 360 above). No error, no warning; the printed totals are just wrong, and the corruption stays invisible until someone re-adds a row by hand.
Risk & Impact
[Correctness] The reported totals are silently wrong. An invoice, commission, or payout calculation built on this script bills the wrong amount.
Cost: Wrong figures shipped to customers, discovered only when someone manually re-adds a row.
Code Comparison
❌ Vulnerable (AI generated)
❌ Vulnerable (AI generated)
✅ Hardened (production ready)
The Fix
Avoid by-reference foreach loops — write back by index — or unset($item) immediately after the reference loop so the reference cannot leak.
Prevention
- [linter] PHP_CodeSniffer / PHPStan rules that flag a foreach-by-reference that is not followed by an unset() on the same variable.
- [ci] A test that asserts every row's value (not just the count) catches the corrupted last element.
- [manual] Prefer index-based writes over references when the agent mutates arrays (rule 3), and require tests that check values, not counts (rule 9).
Why This Happened
Root cause: the prompt omitted Rule 3 and Rule 9 — the guardrails that would have caught this. See the full guide →
