Tutorials Logic, IN info@tutorialslogic.com

PHP Loops: foreach, for, while, Control, and Safe Iteration

PHP Loop Flow

PHP provides foreach for iterables, for for explicit counters, while for pre-checked state, and do-while for one required first execution. Every loop still needs initialization, a precise continuation rule, progress, and a bounded termination policy.

Reliable loops preserve collection ownership, use break and continue visibly, batch external work, stream large inputs, and test empty, boundary, retry, mutation, and partial-failure paths.

foreach Collections

Use key => value when the array key carries meaning. Iterating by reference can modify the original array, but unset the reference variable afterward to prevent later assignments from changing the last item.

Render Course Progress

Render Course Progress
<?php
$progress = ['syntax' => 100, 'forms' => 60, 'pdo' => 20];

foreach ($progress as $topic => $percent) {
    echo "{$topic}: {$percent}%" . PHP_EOL;
}
Output
syntax: 100%
forms: 60%
pdo: 20%

for Counters

A for loop keeps initialization, condition, and update together. It fits an index or an exact repetition count.

Generate Page Numbers

Generate Page Numbers
<?php
for ($page = 1; $page <= 3; $page++) {
    echo "Page {$page}" . PHP_EOL;
}
Output
Page 1
Page 2
Page 3

while Conditions

Use while when the number of attempts is not known in advance. The body must move the condition toward false or deliberately break.

Retry with a Limit

Retry with a Limit
<?php
$attempt = 1;
$connected = false;

while (!$connected && $attempt <= 3) {
    echo "Attempt {$attempt}" . PHP_EOL;
    $connected = $attempt === 2;
    $attempt++;
}
Output
Attempt 1
Attempt 2

break and continue

break exits the loop. continue skips the rest of the current iteration. Keep either statement close to the condition it serves.

Skip Invalid Rows

Skip Invalid Rows
<?php
$emails = ['maya@example.com', 'invalid', 'lee@example.com'];

foreach ($emails as $email) {
    if (filter_var($email, FILTER_VALIDATE_EMAIL) === false) {
        continue;
    }

    echo $email . PHP_EOL;
}
Output
maya@example.com
lee@example.com

Loop Failure Signals

Symptom Cause to inspect Correction
Loop never ends Condition never changes Update state or add a justified limit
First or last item missing Boundary uses < versus <= Write the expected index range
Original array changed foreach used a reference Avoid the reference or unset it
Slow request Unbounded data or repeated I/O Limit input and batch external work

Loop Selection

Choose a loop from the shape of the work. Use `foreach` for values from an array or Traversable object, `for` for a counter with a known progression, `while` when continuation depends on changing state, and `do-while` only when one first attempt is required before the test.

Every loop has initialization, a continuation decision, a body, and progress toward termination even when those parts are written in different places. State each part before coding. This exposes missing updates and off-by-one boundaries early.

PHP converts the loop condition to boolean. Avoid relying on loose truthiness when zero, an empty string, or an empty array is valid state. Write a comparison that names the actual boundary, such as `$attempt < $maxAttempts`.

An unbounded loop is a resource policy, not merely syntax. Requests, queue workers, file readers, and paginated clients need record, byte, attempt, or time limits plus a defined result when the limit is reached.

  • Use foreach for iterable values and keys.
  • Use for when counter progression is central.
  • Use while for state-driven repetition.
  • Define termination and a maximum before running external work.

Foreach Semantics

`foreach` accepts arrays and Traversable objects. The key form preserves each source key, while the value-only form ignores it. Iterating an unsupported or uninitialized value is an error; validate nullable external data before the loop instead of suppressing the diagnostic.

Normal value iteration assigns the current value to the loop variable. Objects inside an array still refer to objects, so mutating an object changes that object even without an ampersand. Reassigning the loop variable does not replace an array element during ordinary value iteration.

Reference iteration with `foreach ($items as &$item)` permits element replacement. The loop variable remains a reference to the final element afterward. Call `unset($item)` immediately after the loop, or a later assignment may silently overwrite that element.

Destructuring can unpack known row shapes, but missing positions produce diagnostics. Validate imported CSV, JSON, and database rows before destructuring. Use named domain objects when the record shape is fixed and important.

A foreach loop does not advance the array internal pointer used by `current()` and `key()`. Do not mix pointer functions and foreach as two competing traversal mechanisms; one explicit iteration model is easier to reason about.

  • Validate that the source is iterable.
  • Distinguish element replacement from object mutation.
  • Unset every by-reference loop variable.
  • Validate row shape before destructuring.

Counter Boundaries

A `for` loop evaluates initialization once, checks its condition before each iteration, and runs its update after each completed body. `continue` still leads to the update expression, which matters when progress is kept in the loop header.

Translate the intended range into plain language before choosing `<` or `<=`: "indexes zero through count minus one" differs from "page numbers one through count." Prefer `foreach` when the counter exists only to index every array element.

All three expressions may contain comma-separated operations or be empty, but compact syntax is not automatically clear syntax. Keep independent state changes in the body and make an intentionally infinite loop obvious with an explicit exit condition.

Do not repeatedly call a costly function in the condition unless its result must change every iteration. Capture stable counts, configuration, or parsed values once. Conversely, do not cache a condition whose changing result is the reason the loop terminates.

  • Write the inclusive and exclusive boundaries first.
  • Keep one primary progression in the loop header.
  • Cache only values that are stable for the whole loop.
  • Test zero, one, and maximum iteration counts.

Conditional Loops

`while` checks its condition before the body, so it can run zero times. It fits reading until end-of-file, retrying until success or a limit, and processing while a queue has approved work. The body must update or observe state that can make the condition false.

`do-while` checks after the body and therefore runs at least once. It fits a first prompt, first fetch, or first calculation whose result determines repetition. Do not use it when the initial action may be forbidden or unnecessary.

Retries need more than a loop counter. Classify retryable failures, apply a deadline, delay repeated attempts where appropriate, stop on permanent errors, and make repeated operations idempotent or safely deduplicated.

Polling loops should release resources between attempts and honor cancellation or process shutdown. A tight empty loop consumes CPU while learning nothing. Prefer event-driven APIs when the platform offers them.

  • Use while when zero executions is valid.
  • Use do-while when the first execution is required.
  • Give retries a deadline and failure classification.
  • Avoid tight polling that consumes CPU.

Run One Validation Pass

Run One Validation Pass
<?php
$attempt = 0;

do {
    $attempt++;
    $accepted = $attempt >= 2;
    echo "Attempt {$attempt}" . PHP_EOL;
} while (!$accepted && $attempt < 3);
Output
Attempt 1
Attempt 2

The body runs once before the condition is checked, while the maximum prevents unlimited attempts.

Loop Control

`break` ends the current `for`, `foreach`, `while`, `do-while`, or `switch`. `continue` skips the remaining statements in the current iteration and moves to the next loop decision. Place either beside the condition it serves and use braces to keep the control path visible.

Both statements accept a positive numeric level for nested structures. `break 2` exits two enclosing eligible structures; `continue 2` advances the appropriate outer loop. Deep numeric control is difficult to maintain, so extract a function or use a named result when nesting becomes substantial.

A `switch` participates in these level counts. Bare `continue` inside a switch behaves like leaving that switch and raises a warning because it is commonly mistaken for continuing an outer loop. Use explicit structure and tests when switch and loops are nested.

Early `continue` can keep the main path flat by rejecting invalid rows first. Early `break` is appropriate after a match or limit. Neither should conceal partial writes; complete or roll back one unit of work before changing loop control.

  • Keep break and continue close to their conditions.
  • Refactor deeply nested numeric levels.
  • Account for switch in nesting levels.
  • Finish one atomic unit before exiting an iteration.

Mutation and I/O

Changing the collection being traversed can skip, repeat, or unexpectedly include elements depending on the data type and operation. Build a new result for filtering, queue mutations explicitly, or document a tested mutation contract rather than relying on incidental traversal behavior.

Do not execute one database query or remote request per element when a batch API or joined query can perform the work once. This N-plus-one pattern scales with the row count and often dominates the cost of the loop itself.

Transactions should match the required atomic unit. One transaction per item allows partial progress; one transaction for the entire batch gives all-or-nothing behavior but holds resources longer. Choose deliberately and record failed item identifiers safely.

Generators and iterators let a loop consume large results incrementally. Streaming reduces peak memory only if downstream code also avoids collecting everything. Close handles and cursors through explicit ownership, including exception paths.

  • Avoid mutating traversal structure without a tested contract.
  • Batch database and remote operations.
  • Choose transaction scope from recovery requirements.
  • Stream large inputs through the complete pipeline.

Loop Verification

Test empty input, one item, the last valid item, an immediate break, every item skipped, maximum iterations, malformed rows, and a failure halfway through. Assert final state and side effects, not only printed output.

For counters, test the exact values visited. For retries, use a fake dependency that succeeds or fails on controlled attempts. For time limits, inject a clock where practical so the suite remains deterministic and fast.

Property-based tests can check that mapping preserves count, filtering returns only accepted values, and batching processes each identifier at most once. A timeout test should still have a hard external ceiling so a defect cannot hang the suite.

Profile with representative collection sizes and dependency latency. Micro-optimizing loop syntax rarely matters beside repeated I/O, accidental copying, quadratic searches, or unbounded memory growth.

  • Assert visited values and final state.
  • Control dependencies and clocks in retry tests.
  • Give every potentially infinite test a hard ceiling.
  • Optimize measured algorithms and I/O patterns first.
Before you move on

Mastery Check

5 checks
  • Choose the loop from the source and termination rule.
  • Write exact boundaries and maximum work limits.
  • Unset foreach reference variables immediately.
  • Batch repeated I/O and define transaction scope.
  • Test visited values, side effects, cancellation, and failures.

Select the Loop

0 of 2 checked

Q1. Which loop is the default choice for an associative array?

Q2. What must a while loop eventually do?

Try this next

Trace Each Iteration

0 of 2 completed

  1. Loop through prices, skip negative values, and calculate the total.
  2. Search an array for the first score above 90 and stop with break.
Browse Free Tutorials

Explore 500+ free tutorials across 20+ languages and frameworks.