Bucles en PHP: while, do-while, for y foreach
En esta lección
Un bucle repite un bloque de código mientras se cumple una condición o una vez por cada elemento de una lista. Gracias a ellos puedes mostrar cien productos con el mismo código que muestra uno, generar las opciones de un <select> o sumar todas las líneas de un pedido. En esta lección aprenderás los cuatro bucles de PHP (while, do-while, for y foreach), a controlarlos con break y continue, a anidarlos y a usarlos dentro de plantillas HTML.
Por qué hacen falta los bucles
Imagina que quieres mostrar los números de los cinco carriles de una piscina. Sin bucles tendrías que escribir:
echo "Carril 1\n";
echo "Carril 2\n";
echo "Carril 3\n";
echo "Carril 4\n";
echo "Carril 5\n";
// Carril 1
// Carril 2
// Carril 3
// Carril 4
// Carril 5
¿Y si fueran 50? ¿O si el número de carriles viniera de una base de datos? Un bucle hace el mismo trabajo en tres líneas, sea cual sea la cantidad:
for ($carril = 1; $carril <= 5; $carril++) {
echo "Carril $carril\n";
}
// Carril 1
// Carril 2
// Carril 3
// Carril 4
// Carril 5
Cada repetición se llama iteración.
while: repetir mientras se cumpla una condición
while (“mientras”) comprueba una condición antes de cada vuelta. Si es verdadera, ejecuta el bloque y vuelve a comprobar; cuando es falsa, sigue con el resto del programa.
$ahorro = 0;
$semanas = 0;
while ($ahorro < 100) {
$ahorro += 15;
$semanas++;
}
echo "Necesitas $semanas semanas para ahorrar 100 € (tendrás $ahorro €)\n";
// Necesitas 7 semanas para ahorrar 100 € (tendrás 105 €)
Úsalo cuando no sabes de antemano cuántas vueltas dará el bucle: depende de algo que cambia dentro. Si la condición es falsa desde el principio, el bloque no se ejecuta ni una vez.
Cuidado con los bucles infinitos
Si nada dentro del bucle hace que la condición acabe siendo falsa, el bucle no termina nunca:
$intentos = 0;
while ($intentos < 3) {
echo "Intento\n";
// falta $intentos++; -> el bucle no termina nunca
}
En la terminal lo paras con Ctrl + C. En el servidor, PHP lo corta al llegar al tiempo máximo de ejecución (max_execution_time, 30 segundos por defecto) con un error fatal. Antes de escribir un while, pregúntate siempre: ¿qué hace que esta condición deje de cumplirse?
do-while: al menos una vez
do-while comprueba la condición al final de cada vuelta. Por eso el bloque se ejecuta siempre al menos una vez:
$dado = 6;
$tiradas = 0;
do {
$tiradas++;
echo "Tirada $tiradas: sale un $dado\n";
$dado = $dado - 2;
} while ($dado > 3);
// Tirada 1: sale un 6
// Tirada 2: sale un 4
Fíjate en el punto y coma tras el while final, que es obligatorio. Es el bucle que menos se usa; encaja cuando primero hay que hacer algo y después decidir si se repite, como tirar un dado hasta que salga un número o generar un código aleatorio hasta que no esté repetido.
for: un número conocido de vueltas
for reúne en una línea las tres partes de un bucle con contador, separadas por punto y coma:
for (inicio; condición; actualización) {
// código
}
- Inicio: se ejecuta una vez, antes de empezar (
$i = 1). - Condición: se comprueba antes de cada vuelta (
$i <= 10). - Actualización: se ejecuta al final de cada vuelta (
$i++).
$precioPlaza = 12;
for ($personas = 1; $personas <= 4; $personas++) {
$total = $personas * $precioPlaza;
echo "$personas persona(s): $total €\n";
}
// 1 persona(s): 12 €
// 2 persona(s): 24 €
// 3 persona(s): 36 €
// 4 persona(s): 48 €
El contador puede avanzar como quieras: de dos en dos, hacia atrás…
for ($i = 10; $i > 0; $i -= 3) {
echo $i, " ";
}
echo "¡Ya!\n";
// 10 7 4 1 ¡Ya!
Usa for cuando sepas cuántas veces quieres repetir: 10 filas, los días de un mes, los años desde 1950 hasta hoy.
foreach: recorrer un array
El bucle que más usarás en PHP es foreach (“para cada”). Recorre un array elemento a elemento, sin contadores ni condiciones:
$material = ["arnés", "casco", "pies de gato", "magnesio"];
foreach ($material as $objeto) {
echo "- $objeto\n";
}
// - arnés
// - casco
// - pies de gato
// - magnesio
En cada vuelta, $objeto contiene el siguiente elemento. El nombre de esa variable lo eliges tú; lo habitual es el plural para el array y el singular para cada elemento ($socios as $socio, $precios as $precio).
Clave y valor
Cada elemento de un array tiene una clave (su posición o su nombre) y un valor. Con la forma as $clave => $valor obtienes las dos cosas:
$horario = [
"lunes" => "Cerrado",
"martes" => "10:00 - 23:00",
"sábado" => "9:00 - 21:00",
];
foreach ($horario as $dia => $horas) {
echo ucfirst($dia), ": $horas\n";
}
// Lunes: Cerrado
// Martes: 10:00 - 23:00
// Sábado: 9:00 - 21:00
Funciona igual con arrays numéricos, donde la clave es la posición (empezando en 0):
$podio = ["Iker", "Carmen", "Saúl"];
foreach ($podio as $posicion => $nombre) {
echo ($posicion + 1) . "º $nombre\n";
}
// 1º Iker
// 2º Carmen
// 3º Saúl
Recorrer filas de datos
En una web real, los datos suelen llegar como una lista de filas, cada una un array asociativo (así devuelve PDO los resultados de una consulta). foreach los recorre de forma natural:
$pedido = [
["producto" => "Cuerda 60 m", "precio" => 120, "cantidad" => 1],
["producto" => "Mosquetón", "precio" => 9.5, "cantidad" => 4],
["producto" => "Cinta exprés", "precio" => 14, "cantidad" => 6],
];
$total = 0;
foreach ($pedido as $linea) {
$subtotal = $linea["precio"] * $linea["cantidad"];
$total += $subtotal;
echo "{$linea['producto']}: $subtotal €\n";
}
echo "Total: $total €\n";
// Cuerda 60 m: 120 €
// Mosquetón: 38 €
// Cinta exprés: 84 €
// Total: 242 €
Este patrón de acumulador (una variable que empieza en 0 y va sumando en cada vuelta) es uno de los más comunes en programación. Con un contador ($n++) cuentas elementos que cumplen algo; con un acumulador sumas sus valores.
Modificar el array: foreach por referencia
Dentro de foreach, la variable del elemento es una copia. Cambiarla no cambia el array:
$precios = [10, 20, 30];
foreach ($precios as $precio) {
$precio = $precio * 1.21;
}
print_r($precios);
// Array
// (
// [0] => 10
// [1] => 20
// [2] => 30
// )
Si quieres modificar el array, tienes dos opciones. La más clara es usar la clave:
$precios = [10, 20, 30];
foreach ($precios as $i => $precio) {
$precios[$i] = $precio * 1.21;
}
echo implode(", ", $precios), "\n";
// 12.1, 24.2, 36.3
La otra es poner & delante de la variable, para recorrer por referencia: la variable deja de ser una copia y pasa a ser el propio elemento. Tiene una trampa: al acabar, la variable sigue apuntando al último elemento. Por eso, tras un foreach por referencia, se hace siempre unset():
$precios = [10, 20, 30];
foreach ($precios as &$precio) {
$precio = $precio * 2;
}
unset($precio); // rompe el vínculo con el último elemento
echo implode(", ", $precios), "\n";
// 20, 40, 60
Si olvidas el unset() y más adelante vuelves a usar $precio en otro bucle, sobrescribirás el último elemento del array sin darte cuenta. Para transformar todos los elementos también existe array_map(), que verás en Funciones de arrays.
break y continue
Dos instrucciones permiten alterar el recorrido normal de cualquier bucle:
breaksale del bucle inmediatamente.continuesalta el resto de la vuelta actual y pasa a la siguiente.
$socios = ["Ana", "Pedro", "Lola", "Hugo", "Elena"];
$buscado = "Lola";
foreach ($socios as $i => $socio) {
if ($socio === $buscado) {
echo "$buscado encontrada en la posición $i\n";
break; // no hace falta seguir buscando
}
}
// Lola encontrada en la posición 2
$temperaturas = [18, -99, 21, 19, -99, 23]; // -99 = sensor sin datos
$suma = 0;
$validas = 0;
foreach ($temperaturas as $t) {
if ($t === -99) {
continue; // ignoramos las lecturas erróneas
}
$suma += $t;
$validas++;
}
echo "Media: ", $suma / $validas, " ºC\n";
// Media: 20.25 ºC
continue evita meter todo el bloque dentro de un if. Dentro de bucles anidados, break 2 sale de dos niveles a la vez; úsalo con moderación, porque cuesta seguirlo.
Bucles anidados
Un bucle puede ir dentro de otro. Por cada vuelta del exterior, el interior hace todas sus vueltas. Es lo que necesitas para recorrer datos en dos dimensiones, como filas y columnas:
$filas = ["A", "B"];
foreach ($filas as $fila) {
for ($asiento = 1; $asiento <= 3; $asiento++) {
echo "$fila$asiento ";
}
echo "\n";
}
// A1 A2 A3
// B1 B2 B3
El bucle interior se ejecuta 3 veces por cada una de las 2 filas: 6 vueltas en total. Ten cuidado con el tamaño: dos bucles de 1.000 vueltas anidados son un millón de iteraciones.
Generar HTML con bucles
Aquí es donde los bucles brillan en una web. Un uso muy típico es rellenar un <select>:
<label for="anio">Año de nacimiento</label>
<select id="anio" name="anio">
<?php for ($anio = 2026; $anio >= 2020; $anio--): ?>
<option value="<?= $anio ?>"><?= $anio ?></option>
<?php endfor; ?>
</select>
Y otro, mostrar una lista de datos. En las plantillas se usa la sintaxis alternativa que viste en Sintaxis y echo: foreach (...): y endforeach;.
<?php
$vias = [
["nombre" => "La Pantera", "grado" => "6a"],
["nombre" => "Ruta del Sol", "grado" => "5c"],
];
?>
<?php if ($vias === []): ?>
<p>No hay vías registradas.</p>
<?php else: ?>
<table>
<tr><th>Vía</th><th>Grado</th></tr>
<?php foreach ($vias as $via): ?>
<tr>
<td><?= htmlspecialchars($via['nombre']) ?></td>
<td><?= htmlspecialchars($via['grado']) ?></td>
</tr>
<?php endforeach; ?>
</table>
<?php endif; ?>
Combinar un condicional para el caso vacío con un foreach para los datos es un patrón que repetirás en casi todas las páginas.
range(): una lista de números
range($inicio, $fin, $paso) crea un array con una serie de números. Junto a foreach, a veces es más legible que un for:
foreach (range(0, 100, 25) as $porcentaje) {
echo "$porcentaje% ";
}
echo "\n";
// 0% 25% 50% 75% 100%
¿Qué bucle elegir?
| Bucle | Cuándo usarlo | Ejemplo |
|---|---|---|
foreach | Recorrer un array | Mostrar productos, sumar un carrito |
for | Repetir un número conocido de veces | Generar años, páginas, filas |
while | Repetir hasta que algo cambie | Ahorrar hasta llegar a una cifra, leer un archivo línea a línea |
do-while | Igual que while, pero al menos una vez | Repetir un intento hasta que salga bien |
En PHP, la mayoría de bucles que escribirás serán foreach. Si estás usando un for con count() para recorrer un array, probablemente un foreach lo haría más simple.
Errores frecuentes
- Bucles infinitos: un
whilecuya condición nunca deja de cumplirse. Asegúrate de que algo dentro del bucle la cambia. - Errores de uno (off by one):
$i <= count($lista)da una vuelta de más, porque las posiciones van de 0 acount - 1. Conforeacheste problema desaparece. - Modificar la variable del
foreachesperando cambiar el array: es una copia. Usa la clave ($array[$i] = ...) o&. - No hacer
unset()tras unforeachpor referencia: la variable sigue enlazada al último elemento. - Olvidar el
;dedo { } while (...);: da un error de sintaxis. - Inicializar el acumulador dentro del bucle: si
$total = 0;está dentro, se reinicia en cada vuelta. Va antes del bucle. - Hacer consultas a la base de datos dentro de un bucle: cien vueltas son cien consultas. Siempre que puedas, trae todos los datos con una sola consulta y recórrelos después.
Resumen
| Concepto | Sintaxis |
|---|---|
| Mientras se cumpla | while (cond) { ... } |
| Al menos una vez | do { ... } while (cond); |
| Con contador | for ($i = 0; $i < 10; $i++) { ... } |
| Recorrer valores | foreach ($lista as $valor) { ... } |
| Clave y valor | foreach ($lista as $clave => $valor) { ... } |
| Modificar el array | foreach ($lista as &$v) { ... } unset($v); |
| Salir del bucle | break; (break 2; sale de dos niveles) |
| Saltar a la siguiente vuelta | continue; |
| En plantillas | foreach (...): ... endforeach;, for (...): ... endfor; |
| Serie de números | range(1, 10), range(0, 100, 25) |
En la siguiente lección aprenderás todo sobre la estructura que recorren tus bucles: arrays.
Pon a prueba lo que has aprendido
¿Te ha quedado claro? Márcala y verás tu progreso en el explorador.