break, continue, and labeled loops
Concept
break and continue alter loop execution flow. Both accept an optional numeric argument specifying how many levels of nested loops to affect — a PHP feature not present in many other languages.
break exits the loop entirely. break 2 exits two levels of nesting simultaneously, avoiding the need for a flag variable. The argument must be a positive integer literal (not a variable) as of PHP 8.0.
continue skips the remainder of the current loop iteration and proceeds to the next. continue 2 skips to the next iteration of the outer loop.
Labeled loop pattern: PHP doesn't have named labels for loops (unlike Java/Go). The numeric argument to break/continue is the only mechanism to escape nested loops. For deeply nested logic, refactoring into a function where you can return early is usually cleaner.
break in switch: In PHP, a switch inside a loop requires break 2 to exit the surrounding loop, because break with no argument exits the switch — not the loop. This is a notorious gotcha.
Code Example
<?php
declare(strict_types=1);
// break — exit current loop
$found = null;
foreach (range(1, 100) as $n) {
if ($n % 7 === 0 && $n % 13 === 0) {
$found = $n;
break; // stop as soon as we find it
}
}
echo $found; // 91
// continue — skip current iteration
$evens = [];
foreach (range(1, 10) as $n) {
if ($n % 2 !== 0) continue;
$evens[] = $n;
}
print_r($evens); // [2,4,6,8,10]
// break 2 — exit nested loops
$matrix = [[1,2,3],[4,5,6],[7,8,9]];
$target = 5;
$found_at = null;
foreach ($matrix as $row => $cols) {
foreach ($cols as $col => $val) {
if ($val === $target) {
$found_at = [$row, $col];
break 2; // exits both foreach loops
}
}
}
echo "Found at row={$found_at[0]}, col={$found_at[1]}"; // row=1, col=1
// continue 2 — skip to next outer iteration
$data = [[1,2,null],[4,5,6],[null,8,9]];
$result = [];
foreach ($data as $row) {
foreach ($row as $val) {
if ($val === null) continue 2; // skip this entire row
}
$result[] = $row; // only rows with no nulls
}
// The switch/break gotcha
$statuses = [200, 301, 404, 500];
foreach ($statuses as $code) {
switch ($code) {
case 200:
echo "OK\n";
break; // breaks the SWITCH, not the foreach
case 404:
echo "Not found, stopping\n";
break 2; // breaks the foreach (exits 2 levels: switch + foreach)
}
}
// Cleaner alternative: refactor into a function
function findFirst(array $matrix, int $target): ?array
{
foreach ($matrix as $row => $cols) {
foreach ($cols as $col => $val) {
if ($val === $target) return [$row, $col];
}
}
return null;
}