Loop Style Preferences
debt(d3/e1/b1/t3)
Closest to 'default linter catches the common case' (d3), phpcs/phpmd/rector can flag for-with-count patterns and suggest foreach conversions via standard rules.
Closest to 'one-line patch or single-call swap' (e1), the quick_fix is a localized loop rewrite — swapping a for($i=0;...) for a foreach is a single-line/single-block change.
Closest to 'minimal commitment' (b1), this is a per-loop stylistic choice with no cross-cutting structural weight; it's a naming/convention-level decision.
Closest to 'minor surprise' (t3), the main gotcha cited in common_mistakes is the foreach-by-reference leak after the loop — a documented but bounded edge case rather than a contradictory behavior.
Also Known As
TL;DR
Explanation
Loop style is a readability decision. PHP offers foreach, for, while, and do-while, plus functional alternatives like array_map, array_filter, and array_reduce. The right choice depends on intent, not personal habit.
Use foreach when iterating over a collection and you only need the elements (and optionally the key). It is the default — it cannot run off the end, does not need a counter, and reads as 'for each item, do this.' Use a classic for loop only when you genuinely need an index, a step other than 1, or simultaneous traversal of related sequences. Use while when the termination condition is not tied to a collection's length (reading a stream, polling a queue, walking a linked structure). Use do-while when the body must execute at least once.
Functional alternatives (array_map, array_filter, array_reduce) express transformation intent rather than mechanics. They shine when the operation is a pure pipeline: 'take these orders, keep paid ones, sum totals.' They are not a universal replacement — multi-step loops, early exits, side effects, and stateful accumulation usually read better as foreach. Recursion is appropriate for genuinely recursive data (trees, nested structures) but is rarely the right choice for flat iteration in PHP because there is no tail-call optimisation.
Within a file, stay consistent. Mixing for ($i=0; $i<count($xs); $i++) with foreach over the same kind of data in adjacent functions is jarring. Avoid common anti-patterns: calling count() inside the for condition on a static array, using foreach with a by-reference variable and forgetting to unset it, or reaching for array_map purely to look clever when foreach would be plainer.
The guiding principle: the loop construct should communicate what the loop is doing. If a reader has to study the header to understand intent, you picked the wrong construct.
Common Misconception
Why It Matters
Common Mistakes
- Using for ($i=0; $i<count($items); $i++) when foreach ($items as $item) would do — the index is unused and count() may even be recomputed each iteration.
- Forgetting to unset() a by-reference variable after foreach ($items as &$item), causing the reference to leak into the next loop.
- Reaching for array_map/array_filter chains when the operation has side effects, early returns, or accumulates state — foreach reads better there.
- Mixing while (list($k, $v) = each(...)) legacy patterns with modern foreach in the same file.
- Using recursion for flat iteration in PHP where it adds stack frames without expressing recursive intent.
- Carrying over legacy while(list($k,$v)=each(...)) patterns when modernising old code instead of converting them to foreach (each() was removed in PHP 8.0).
Avoid When
- Performance-critical inner loops where a specific construct has measured wins — micro-style consistency comes second.
- Translating algorithms from pseudocode or another language where the original loop shape aids review and verification.
- Working in a legacy codebase with a strong existing convention — match it rather than introduce a second style.
When To Use
- Choosing between for, foreach, while in new code where any would work syntactically.
- Refactoring index-based loops that never use the index into foreach for clarity.
- Deciding whether a transformation belongs in a foreach or in an array_map/filter/reduce pipeline.
- Establishing or documenting a team style guide for iteration patterns.
Code Examples
<?php
// Index never used — foreach would be clearer
for ($i = 0; $i < count($orders); $i++) {
echo $orders[$i]->total() . "\n";
}
// count() recomputed every iteration
for ($i = 0; $i < count($rows); $i++) {
process($rows[$i]);
}
// Reference leak — $item still references last element
foreach ($items as &$item) {
$item = strtoupper($item);
}
// later...
foreach ($items as $item) { /* surprise: writes to last element */ }
// array_map abused for side effects
array_map(function ($u) {
sendEmail($u);
log("sent to {$u->email}");
}, $users);
<?php
// Collection iteration — foreach states intent
foreach ($orders as $order) {
echo $order->total() . "\n";
}
// Genuine need for index
for ($i = 0, $n = count($rows); $i < $n; $i++) {
if ($i > 0) echo ", ";
echo $rows[$i]->name;
}
// Reference loop with explicit cleanup
foreach ($items as &$item) {
$item = strtoupper($item);
}
unset($item);
// Condition-driven: while is correct
while (($line = fgets($handle)) !== false) {
process($line);
}
// Pure transformation: array_map fits
$totals = array_map(fn($o) => $o->total(), $orders);
// Side effects belong in foreach
foreach ($users as $user) {
sendEmail($user);
log("sent to {$user->email}");
}