Módulo 04: Estructuras de control
Objetivos del módulo
- Tomar decisiones con
if,elseifyelse - Usar la sintaxis alternativa (
if: ... endif;) en plantillas HTML - Comprender
switchy su comportamiento de “caída” (fallthrough) - Dominar la expresión
matchde PHP 8 y saber cuándo preferirla aswitch - Repetir código con
while,do-while,foryforeach - Recorrer arrays con
foreachpor clave y por referencia, evitando la referencia colgante - Controlar los bucles con
breakycontinue, incluidos los niveles - Construir programas completos: tabla de multiplicar, calculadora, FizzBuzz y un menú de consola
1. ¿Qué son las estructuras de control?
📘 Concepto: Por defecto, PHP ejecuta las instrucciones una detrás de otra, de arriba abajo. Las estructuras de control permiten cambiar ese flujo: elegir qué código ejecutar según una condición (estructuras condicionales) o repetir código varias veces (bucles).
Piensa en un GPS: normalmente sigue recto (secuencia), pero en cada cruce decide hacia dónde girar (condicional), y en una rotonda puede dar vueltas hasta encontrar la salida (bucle).
2. if, elseif y else
if simple
Ejecuta un bloque solo si la condición es verdadera:
<?php
declare(strict_types=1);
$temperatura = 32;
if ($temperatura > 30) {
echo 'Hace mucho calor. ¡Bebe agua!' . PHP_EOL;
}
if / else
<?php
declare(strict_types=1);
$edad = 16;
if ($edad >= 18) {
echo 'Puedes votar.' . PHP_EOL;
} else {
$faltan = 18 - $edad;
echo "Podrás votar dentro de $faltan años." . PHP_EOL;
}
if / elseif / else
Cuando hay varias opciones, PHP comprueba las condiciones en orden y ejecuta solo la primera que se cumple:
<?php
declare(strict_types=1);
$nota = 7.8;
if ($nota >= 9) {
$calificacion = 'Sobresaliente';
} elseif ($nota >= 7) {
$calificacion = 'Notable';
} elseif ($nota >= 6) {
$calificacion = 'Bien';
} elseif ($nota >= 5) {
$calificacion = 'Suficiente';
} else {
$calificacion = 'Insuficiente';
}
echo "Nota $nota: $calificacion" . PHP_EOL; // Nota 7.8: Notable
⚠️ Importante: El orden de las condiciones importa. Si pusieras primero
$nota >= 5, una nota de 9,5 entraría ahí y saldría “Suficiente”. Ordena de la condición más restrictiva a la más general.
💡 Consejo: En PHP puedes escribir
elseif(junto) oelse if(separado) y funcionan igual con llaves. Usaelseif: es la forma recomendada por el estándar PER y la única válida en la sintaxis alternativa.
Llaves siempre
PHP permite omitir las llaves si el bloque tiene una única instrucción, pero es una fuente clásica de errores:
<?php
$stock = 0;
// ❌ Peligroso: parece que las dos líneas dependen del if, pero solo la primera
if ($stock === 0)
echo 'Agotado';
echo 'Avisaremos al proveedor'; // ¡Se ejecuta SIEMPRE!
// ✅ Con llaves no hay ambigüedad
if ($stock === 0) {
echo 'Agotado';
echo 'Avisaremos al proveedor';
}
Cláusulas de guarda: menos anidamiento
Cuando hay muchas condiciones anidadas, el código se convierte en una “flecha” difícil de leer. La técnica de las cláusulas de guarda consiste en descartar primero los casos inválidos y salir pronto:
<?php
declare(strict_types=1);
// Cada problema se descarta y se sale; el caso normal queda al final
function puedeComprarMejor(?array $usuario, float $importe): string
{
if ($usuario === null) {
return 'Debes iniciar sesión';
}
if (!$usuario['activo']) {
return 'Cuenta desactivada';
}
if ($usuario['saldo'] < $importe) {
return 'Saldo insuficiente';
}
return 'Compra realizada';
}
echo puedeComprarMejor(['activo' => true, 'saldo' => 50.0], 30.0) . PHP_EOL; // Compra realizada
3. Sintaxis alternativa para plantillas HTML
Cuando mezclas PHP con mucho HTML, las llaves { } se pierden entre las etiquetas. PHP ofrece una sintaxis alternativa que sustituye la llave de apertura por : y la de cierre por una palabra clave (endif, endforeach, endfor, endwhile, endswitch):
<?php
declare(strict_types=1);
$usuario = ['nombre' => 'Rocío', 'rol' => 'admin'];
$notificaciones = 3;
?>
<nav>
<?php if ($usuario === null): ?>
<a href="/login">Iniciar sesión</a>
<?php elseif ($usuario['rol'] === 'admin'): ?>
<a href="/admin">Panel de administración</a>
<span>Hola, <?= $usuario['nombre'] ?></span>
<?php else: ?>
<span>Hola, <?= $usuario['nombre'] ?></span>
<?php endif; ?>
<?php if ($notificaciones > 0): ?>
<span class="badge"><?= $notificaciones ?></span>
<?php endif; ?>
</nav>
| Con llaves | Sintaxis alternativa |
|---|---|
if (...) { ... } | if (...): ... endif; |
} elseif (...) { | elseif (...): |
} else { | else: |
foreach (...) { ... } | foreach (...): ... endforeach; |
for (...) { ... } | for (...): ... endfor; |
while (...) { ... } | while (...): ... endwhile; |
switch (...) { ... } | switch (...): ... endswitch; |
💡 Consejo: Usa la sintaxis alternativa solo en las plantillas (ficheros donde predomina el HTML) y las llaves en el código PHP puro. Es la convención de WordPress y de muchas aplicaciones. Cuando tu
endif;está a 40 líneas de suif, se agradece saber qué estructura cierra.
⚠️ Importante: No mezcles estilos en la misma estructura: un
if (...):debe cerrarse conendif;, nunca con}.
4. switch
switch compara una expresión con varios valores (case) y ejecuta el código del que coincida:
<?php
declare(strict_types=1);
$dia = 'martes';
switch ($dia) {
case 'lunes':
echo 'Reunión de equipo a las 9:00';
break;
case 'martes':
echo 'Revisión de código';
break;
case 'viernes':
echo 'Despliegue semanal';
break;
default:
echo 'Día de desarrollo normal';
}
// Revisión de código
case valor:marca un punto de entrada.breaksale delswitch.default:se ejecuta si ningúncasecoincide (es opcional).
La “caída” (fallthrough)
Si olvidas el break, PHP sigue ejecutando los case siguientes aunque no coincidan:
<?php
$nivel = 2;
switch ($nivel) {
case 1:
echo 'Nivel 1 ';
case 2:
echo 'Nivel 2 ';
case 3:
echo 'Nivel 3 ';
break;
case 4:
echo 'Nivel 4 ';
}
// Nivel 2 Nivel 3
A veces se usa a propósito para agrupar casos:
<?php
declare(strict_types=1);
$mes = 4;
switch ($mes) {
case 12:
case 1:
case 2:
$estacion = 'Invierno';
break;
case 3:
case 4:
case 5:
$estacion = 'Primavera';
break;
case 6:
case 7:
case 8:
$estacion = 'Verano';
break;
default:
$estacion = 'Otoño';
}
echo $estacion; // Primavera
⚠️ Importante:
switchcompara con==(igualdad débil), con todas las sorpresas que viste en el módulo 03. Por ejemplo, si$x = '1', entrará encase 1:. Esto, junto con la caída accidental por olvidar unbreak, es la razón por la que PHP 8 introdujomatch.
5. match (PHP 8)
📘 Concepto:
matches una expresión que compara un valor con varias alternativas de forma estricta (===) y devuelve el resultado de la que coincide. Es como una máquina expendedora: introduces un código y te devuelve exactamente un producto.
<?php
declare(strict_types=1);
$codigoHttp = 404;
$mensaje = match ($codigoHttp) {
200 => 'OK',
201 => 'Creado',
301, 302 => 'Redirección', // Varios valores separados por comas
404 => 'No encontrado',
500 => 'Error interno del servidor',
default => 'Código desconocido',
};
echo $mensaje; // No encontrado
Características de match
| Característica | Explicación |
|---|---|
| Devuelve un valor | Se puede asignar a una variable, pasar a una función o usar en return |
| Comparación estricta | Usa ===: '1' no coincide con 1 |
Sin break | Cada rama ejecuta una sola expresión y termina; no hay caída |
| Varios valores por rama | 301, 302 => ... |
| Exhaustivo | Si ningún valor coincide y no hay default, lanza UnhandledMatchError |
| Coma final permitida | Puedes dejar , tras la última rama (recomendado) |
UnhandledMatchError
<?php
declare(strict_types=1);
$talla = 'XXL';
$precio = match ($talla) {
'S', 'M' => 15.0,
'L', 'XL' => 17.5,
};
// Fatal error: Uncaught UnhandledMatchError: Unhandled match case 'XXL'
Esto es una ventaja: en lugar de continuar con un valor incorrecto (como pasaría con un switch sin default), el programa avisa del caso que no has contemplado. Puedes incluso lanzar tu propia excepción en el default:
<?php
declare(strict_types=1);
$talla = 'XXL';
$precio = match ($talla) {
'S', 'M' => 15.0,
'L', 'XL' => 17.5,
default => throw new InvalidArgumentException("Talla no válida: $talla"),
};
match(true): condiciones en lugar de valores
Si pasas true como valor, cada rama puede ser una condición. Se ejecuta la primera que valga true. Es la forma moderna de sustituir una cadena de if/elseif que asigna un valor:
<?php
declare(strict_types=1);
$nota = 7.8;
$calificacion = match (true) {
$nota >= 9 => 'Sobresaliente',
$nota >= 7 => 'Notable',
$nota >= 6 => 'Bien',
$nota >= 5 => 'Suficiente',
default => 'Insuficiente',
};
echo $calificacion; // Notable
<?php
declare(strict_types=1);
$imc = 27.3;
$categoria = match (true) {
$imc < 18.5 => 'Bajo peso',
$imc < 25 => 'Peso normal',
$imc < 30 => 'Sobrepeso',
default => 'Obesidad',
};
⚠️ Importante: En
match(true)las condiciones deben devolver exactamentetrue(comparación estricta). Una expresión que devuelva1o una cadena no vacía no coincidirá. Usa comparaciones (>=,===) o funciones que devuelvan booleanos.
Tabla comparativa: switch frente a match
| Aspecto | switch | match |
|---|---|---|
| Tipo | Instrucción | Expresión (devuelve valor) |
| Comparación | == (débil) | === (estricta) |
break | Necesario (si no, cae al siguiente caso) | No existe; nunca hay caída |
| Varios valores | Varios case seguidos | valor1, valor2 => |
| Cuerpo de cada rama | Varias instrucciones | Una expresión |
| Sin coincidencia | No hace nada (o default) | UnhandledMatchError |
| Disponible desde | Siempre | PHP 8.0 |
| Úsalo cuando… | Cada caso necesita varias instrucciones | Quieres obtener un valor a partir de otro |
💡 Consejo: En código nuevo, empieza pensando en
match. Si cada rama necesita varias instrucciones, extrae ese código a funciones ('pdf' => generarPdf($datos)) o usaif/elseif.switchsigue siendo válido, pero ya no es la primera opción.
6. El bucle while
📘 Concepto: Un bucle repite un bloque de código.
while(“mientras”) repite el bloque mientras la condición sea verdadera. Comprueba la condición antes de cada vuelta, así que puede no ejecutarse ninguna vez.
Úsalo cuando no sabes cuántas vueltas darás. Por ejemplo, cuánto tarda un capital en duplicarse:
<?php
declare(strict_types=1);
$capital = 10000.0;
$objetivo = $capital * 2;
$interesAnual = 0.035;
$anios = 0;
while ($capital < $objetivo) {
$capital *= 1 + $interesAnual;
$anios++;
}
printf("Tardarás %d años. Capital final: %.2f €\n", $anios, $capital);
// Tardarás 21 años. Capital final: 20594.31 €
⚠️ Importante: Si la condición nunca se vuelve falsa, tienes un bucle infinito y el script no termina (en el navegador acabará con el error Maximum execution time exceeded tras 30 segundos por defecto). Asegúrate de que algo dentro del bucle hace avanzar la condición hacia su fin.
7. El bucle do-while
Igual que while, pero comprueba la condición después de cada vuelta. Por tanto, el bloque se ejecuta al menos una vez:
<?php
declare(strict_types=1);
$i = 10;
do {
echo "Vuelta con i = $i" . PHP_EOL;
$i++;
} while ($i < 5);
// Vuelta con i = 10 ← se ejecuta una vez aunque la condición sea falsa
Su uso típico es repetir hasta que algo sea válido, como pedir datos al usuario:
<?php
declare(strict_types=1);
do {
$entrada = readline('Introduce un número del 1 al 10: ');
$numero = filter_var($entrada, FILTER_VALIDATE_INT);
} while ($numero === false || $numero < 1 || $numero > 10);
echo "Has elegido el $numero" . PHP_EOL;
8. El bucle for
for es ideal cuando sabes cuántas vueltas quieres dar. Reúne en una línea las tres partes del control del bucle:
<?php
for (inicialización; condición; actualización) {
// cuerpo
}
<?php
declare(strict_types=1);
for ($i = 1; $i <= 5; $i++) {
echo "Vuelta número $i" . PHP_EOL;
}
flowchart TD
A["1. Inicialización: $i = 1"] --> B{"2. ¿$i <= 5?"}
B -->|Sí| C["3. Ejecuta el cuerpo"]
C --> D["4. Actualización: $i++"]
D --> B
B -->|No| E[Fin del bucle]
💡 Consejo: Si recorres un array con
for, calcula su tamaño una sola vez fuera del bucle:$total = count($datos); for ($i = 0; $i < $total; $i++). Aunque, para recorrer arrays, casi siempre es mejorforeach.
9. El bucle foreach
foreach es el bucle más usado en PHP. Recorre todos los elementos de un array (o de un objeto iterable) sin preocuparte de índices ni tamaños.
Solo valores
<?php
declare(strict_types=1);
$frutas = ['manzana', 'pera', 'naranja'];
foreach ($frutas as $fruta) {
echo "Me gusta la $fruta" . PHP_EOL;
}
Clave y valor
<?php
declare(strict_types=1);
$precios = [
'café' => 1.20,
'té' => 1.50,
'zumo' => 2.75,
];
foreach ($precios as $producto => $precio) {
echo "$producto: $precio €" . PHP_EOL;
}
// café: 1.2 €
// té: 1.5 €
// zumo: 2.75 €
foreach en plantillas HTML
<?php
declare(strict_types=1);
$menu = ['Inicio' => '/', 'Productos' => '/productos', 'Contacto' => '/contacto'];
$actual = '/productos';
?>
<ul class="menu">
<?php foreach ($menu as $texto => $url): ?>
<li class="<?= $url === $actual ? 'activo' : '' ?>">
<a href="<?= $url ?>"><?= $texto ?></a>
</li>
<?php endforeach; ?>
</ul>
Modificar elementos: foreach por referencia
Dentro de un foreach normal, $valor es una copia. Modificarla no cambia el array:
<?php
$precios = [10, 20, 30];
foreach ($precios as $precio) {
$precio *= 2; // Solo cambia la copia
}
print_r($precios); // [10, 20, 30] ← sin cambios
Con & trabajas sobre el elemento original:
<?php
$precios = [10, 20, 30];
foreach ($precios as &$precio) {
$precio *= 2;
}
unset($precio); // ⚠️ ¡Imprescindible! (ver a continuación)
print_r($precios); // [20, 40, 60]
El peligro de la referencia colgante
Tras un foreach por referencia, la variable $precio sigue siendo una referencia al último elemento. Si la reutilizas, modificarás el array sin querer:
<?php
$numeros = [1, 2, 3];
foreach ($numeros as &$n) {
$n *= 10;
}
// Aquí $n sigue apuntando a $numeros[2]
foreach ($numeros as $n) { // ¡Cada vuelta ASIGNA un valor a $numeros[2]!
// nada
}
print_r($numeros);
// Array ( [0] => 10 [1] => 20 [2] => 20 ) ← ¡el 30 ha desaparecido!
Qué ocurre en el segundo bucle:
| Vuelta | $n recibe | Como $n es $numeros[2], el array queda |
|---|---|---|
| 1 | 10 | [10, 20, 10] |
| 2 | 20 | [10, 20, 20] |
| 3 | 20 (el valor actual de $numeros[2]) | [10, 20, 20] |
⚠️ Importante: Después de un
foreachpor referencia, escribe siempreunset($variable);inmediatamente después del bucle. O mejor aún: evita las referencias y crea un array nuevo (conarray_map(), módulo 05) o modifica por clave:foreach ($precios as $i => $precio) { $precios[$i] = $precio * 2; }.
10. break y continue
| Instrucción | Efecto |
|---|---|
break | Sale del bucle por completo |
continue | Salta el resto de la vuelta actual y pasa a la siguiente |
break: salir del bucle
<?php
declare(strict_types=1);
$pedidos = [120, 85, 0, 230, 45];
foreach ($pedidos as $posicion => $importe) {
if ($importe === 0) {
echo "Pedido vacío en la posición $posicion. Se detiene el proceso." . PHP_EOL;
break;
}
echo "Procesando pedido de $importe €" . PHP_EOL;
}
// Procesando pedido de 120 €
// Procesando pedido de 85 €
// Pedido vacío en la posición 2. Se detiene el proceso.
continue: saltar a la siguiente vuelta
<?php
declare(strict_types=1);
$usuarios = [
['nombre' => 'Ana', 'activo' => true],
['nombre' => 'Pedro', 'activo' => false],
['nombre' => 'Lucía', 'activo' => true],
];
foreach ($usuarios as $usuario) {
if (!$usuario['activo']) {
continue; // Ignora a los inactivos
}
echo "Enviando boletín a {$usuario['nombre']}" . PHP_EOL;
}
// Enviando boletín a Ana
// Enviando boletín a Lucía
Niveles: break 2 y continue 2
Con bucles anidados, break y continue afectan solo al bucle más interno. Puedes indicar un número de niveles:
<?php
declare(strict_types=1);
// Buscar la primera butaca libre en un cine (filas x asientos)
$sala = [
[true, true, true], // Fila 1: todas ocupadas
[true, false, true], // Fila 2: el asiento 2 está libre
[false, false, false], // Fila 3: libres
];
$encontrada = null;
foreach ($sala as $fila => $asientos) {
foreach ($asientos as $asiento => $ocupado) {
if (!$ocupado) {
$encontrada = 'Fila ' . ($fila + 1) . ', asiento ' . ($asiento + 1);
break 2; // Sale de LOS DOS bucles
}
}
}
echo $encontrada ?? 'Sala completa'; // Fila 2, asiento 2
<?php
// continue 2: pasa a la siguiente vuelta del bucle EXTERIOR
for ($i = 1; $i <= 3; $i++) {
for ($j = 1; $j <= 3; $j++) {
if ($j === 2) {
continue 2;
}
echo "$i-$j ";
}
}
// 1-1 2-1 3-1
💡 Consejo:
break 2es útil, pero si te encuentras conbreak 3o bucles muy anidados, probablemente el código mejoraría extrayendo la búsqueda a una función y usandoreturnpara salir (lo verás en el módulo 07).
11. Ejemplos completos
Ejemplo 1: Tabla de multiplicar en HTML
<?php
declare(strict_types=1);
// Número recibido por la URL (?n=7), por defecto 5 y limitado entre 1 y 20
$n = (int) ($_GET['n'] ?? 5);
$n = max(1, min(20, $n));
?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>Tabla del <?= $n ?></title>
<style>
table { border-collapse: collapse; font-family: system-ui, sans-serif; }
td, th { border: 1px solid #ccc; padding: .4rem 1rem; text-align: right; }
tr:nth-child(even) { background: #f3f3f9; }
.destacado { background: #777bb3; color: white; }
</style>
</head>
<body>
<h1>Tabla de multiplicar del <?= $n ?></h1>
<form method="get">
<label>Número: <input type="number" name="n" min="1" max="20" value="<?= $n ?>"></label>
<button>Ver tabla</button>
</form>
<table>
<tr><th>Operación</th><th>Resultado</th></tr>
<?php for ($i = 1; $i <= 10; $i++): ?>
<tr class="<?= $i === 10 ? 'destacado' : '' ?>">
<td><?= $n ?> × <?= $i ?></td>
<td><?= $n * $i ?></td>
</tr>
<?php endfor; ?>
</table>
</body>
</html>
💡 Consejo: Fíjate en la validación:
(int)convierte la entrada en entero (si alguien escribe?n=abc, obtiene0) ymax(1, min(20, $n))la limita al rango permitido. Nunca confíes en lo que llega por la URL.
Ejemplo 2: Calculadora con match
<?php
declare(strict_types=1);
function calcular(float $a, string $operador, float $b): float
{
return match ($operador) {
'+' => $a + $b,
'-' => $a - $b,
'*', 'x' => $a * $b,
'/' => $b != 0.0
? $a / $b
: throw new DivisionByZeroError('No se puede dividir entre cero'),
'%' => fmod($a, $b),
'^' => $a ** $b,
default => throw new InvalidArgumentException("Operador desconocido: $operador"),
};
}
$operaciones = [
[12, '+', 30],
[7, '/', 2],
[2, '^', 10],
[5, '*', 1.5],
];
foreach ($operaciones as [$a, $op, $b]) {
echo "$a $op $b = " . calcular($a, $op, $b) . PHP_EOL;
}
// 12 + 30 = 42
// 7 / 2 = 3.5
// 2 ^ 10 = 1024
// 5 * 1.5 = 7.5
📘 Concepto:
foreach ($operaciones as [$a, $op, $b])desestructura cada sub-array en tres variables. Lo verás en detalle en el módulo 05.
Ejemplo 3: FizzBuzz
Un clásico de las entrevistas de trabajo: imprime los números del 1 al 100, pero los múltiplos de 3 se sustituyen por “Fizz”, los de 5 por “Buzz” y los de ambos por “FizzBuzz”.
Con match(true) la solución queda muy compacta. Fíjate en que el múltiplo de 15 (de 3 y de 5 a la vez) debe ir primero, porque match se queda con la primera condición que se cumple:
<?php
declare(strict_types=1);
for ($i = 1; $i <= 100; $i++) {
echo match (true) {
$i % 15 === 0 => 'FizzBuzz',
$i % 3 === 0 => 'Fizz',
$i % 5 === 0 => 'Buzz',
default => $i,
} . PHP_EOL;
}
Ejemplo 4: Menú interactivo en la consola
readline() muestra un mensaje en la terminal y devuelve lo que escribe el usuario. Con un bucle do-while y un switch construimos un menú:
<?php
declare(strict_types=1);
// gestor-tareas.php - ejecútalo con: php gestor-tareas.php
$tareas = [];
do {
echo PHP_EOL . '=== GESTOR DE TAREAS ===' . PHP_EOL;
echo '1. Añadir tarea' . PHP_EOL;
echo '2. Listar tareas' . PHP_EOL;
echo '3. Completar tarea' . PHP_EOL;
echo '0. Salir' . PHP_EOL;
$entrada = readline('Elige una opción: ');
// readline() devuelve false si se cierra la entrada (Ctrl+D / Ctrl+Z): salimos
$opcion = $entrada === false ? '0' : trim($entrada);
switch ($opcion) {
case '1':
$texto = trim((string) readline('Descripción: '));
if ($texto === '') {
echo '⚠ La descripción no puede estar vacía.' . PHP_EOL;
break;
}
$tareas[] = ['texto' => $texto, 'hecha' => false];
echo '✔ Tarea añadida.' . PHP_EOL;
break;
case '2':
if (count($tareas) === 0) {
echo 'No hay tareas.' . PHP_EOL;
break;
}
foreach ($tareas as $i => $tarea) {
$marca = $tarea['hecha'] ? '[x]' : '[ ]';
echo ($i + 1) . ". $marca {$tarea['texto']}" . PHP_EOL;
}
break;
case '3':
$numero = (int) readline('Número de tarea: ');
if (!isset($tareas[$numero - 1])) {
echo '⚠ Esa tarea no existe.' . PHP_EOL;
break;
}
$tareas[$numero - 1]['hecha'] = true;
echo '✔ Tarea completada.' . PHP_EOL;
break;
case '0':
echo '¡Hasta pronto!' . PHP_EOL;
break;
default:
echo '⚠ Opción no válida.' . PHP_EOL;
}
} while ($opcion !== '0');
📘 Concepto: Aquí usamos
switcha propósito: cada opción ejecuta varias instrucciones y no devuelve un valor, que es justo el caso en queswitchsigue siendo adecuado. Losbreakdentro de losifsalen delswitch(no deldo-while), lo que nos permite “abandonar” una opción a mitad.
💡 Consejo:
readline()solo funciona en la línea de comandos y requiere la extensión readline (incluida en las compilaciones habituales de Windows, Linux y Docker). Si no estuviera disponible, puedes usarfgets(STDIN)como alternativa:echo 'Opción: '; $opcion = trim((string) fgets(STDIN));.
12. Buenas prácticas y errores comunes
| Error | Ejemplo | Solución |
|---|---|---|
| Asignar en lugar de comparar | if ($rol = 'admin') (¡siempre verdadero!) | if ($rol === 'admin') |
Olvidar break en switch | Se ejecutan varios case | Añade break o usa match |
| Bucle infinito | while ($i < 10) { echo $i; } sin $i++ | Asegura que la condición avanza |
| Error por uno (off-by-one) | for ($i = 0; $i <= count($a); $i++) | Usa < o, mejor, foreach |
| Referencia colgante | Reutilizar $v tras foreach (... as &$v) | unset($v) justo después |
| Modificar el array que recorres | Añadir elementos dentro de su foreach | Construye un array nuevo |
| Anidamiento excesivo | 4 o 5 niveles de if | Cláusulas de guarda y funciones |
match sin default con datos externos | UnhandledMatchError inesperado | Añade default o valida antes |
💡 Consejo: Algunos programadores escriben las comparaciones al revés,
if ('admin' === $rol)(estilo “Yoda”), para que un=accidental dé error de sintaxis. Con===, análisis estático y un buen editor, no es necesario, pero lo verás en proyectos como WordPress.
13. Ejercicios
Ejercicio 1: Clasificador de edades
Dada una edad, muestra la etapa vital con match(true): bebé (0-2), infancia (3-11), adolescencia (12-17), juventud (18-29), adultez (30-64) y jubilación (65 o más). Si la edad es negativa o mayor de 120, lanza una InvalidArgumentException desde el default. Prueba con al menos siete edades usando un foreach.
Ejercicio 2: Días del mes
Crea una función diasDelMes(int $mes, int $anio): int que use match para devolver los días de cada mes. Para febrero, comprueba si el año es bisiesto: lo es si es divisible entre 4, salvo los divisibles entre 100 que no lo son entre 400. Comprueba que diasDelMes(2, 2024) devuelve 29 y diasDelMes(2, 1900) devuelve 28.
Ejercicio 3: Tablero de ajedrez
Genera con dos bucles for anidados un tablero de 8x8 en HTML (una <table> con celdas de 50x50 px). Las casillas deben alternar blanco y negro: una casilla es oscura si ($fila + $columna) % 2 === 1. Usa la sintaxis alternativa (for: ... endfor;) en la plantilla.
Ejercicio 4: Adivina el número
Programa un juego de consola: el ordenador elige un número con random_int(1, 100) y el usuario tiene 7 intentos para adivinarlo. Tras cada intento, indica “mayor” o “menor”. Usa do-while y break cuando acierte. Al final muestra “¡Has ganado en N intentos!” o “Has perdido. El número era X”.
Ejercicio 5: Estadísticas de ventas
Dado este array de ventas mensuales:
<?php
$ventas = [
'enero' => 12500, 'febrero' => 9800, 'marzo' => 15300,
'abril' => 0, 'mayo' => 17200, 'junio' => 21000,
];
Con un único foreach, calcula: el total, la media de los meses con ventas (ignora los meses a 0 con continue), el mes con más ventas y el mes con menos ventas (sin contar los meses a 0). No uses funciones de arrays como max() o array_sum().
Ejercicio 6: Números primos
Muestra todos los números primos menores que 100. Un número es primo si solo es divisible por 1 y por sí mismo. Usa un bucle externo para los candidatos y uno interno para buscar divisores, con continue 2 en cuanto encuentres un divisor. Optimización: basta con buscar divisores hasta la raíz cuadrada del número.
Ejercicio 7: Corrige la referencia colgante
Este código debería subir un 10 % los precios y luego mostrarlos, pero el último precio sale mal. Explica por qué y corrígelo de dos formas distintas (con unset y sin usar referencias):
<?php
$precios = ['camiseta' => 15.0, 'pantalón' => 35.0, 'zapatillas' => 60.0];
foreach ($precios as &$precio) {
$precio = round($precio * 1.10, 2);
}
foreach ($precios as $producto => $precio) {
echo "$producto: $precio €" . PHP_EOL;
}
print_r($precios);
Resumen
| Estructura | Sintaxis | Uso |
|---|---|---|
if | if (cond) { } elseif (cond) { } else { } | Decisiones generales |
| Sintaxis alternativa | if (cond): ... endif; | Plantillas HTML |
switch | switch ($x) { case 1: ...; break; default: ... } | Varias instrucciones por caso; compara con == |
match | $r = match ($x) { 1, 2 => 'a', default => 'b' }; | Obtener un valor; compara con === |
match(true) | match (true) { $n > 5 => 'a', default => 'b' } | Sustituir if/elseif que asignan |
UnhandledMatchError | — | match sin coincidencia ni default |
while | while (cond) { } | Repetir sin saber cuántas veces (0 o más) |
do-while | do { } while (cond); | Repetir al menos una vez |
for | for ($i = 0; $i < $n; $i++) { } | Número de vueltas conocido |
foreach | foreach ($array as $clave => $valor) { } | Recorrer arrays |
| Por referencia | foreach ($a as &$v) { } unset($v); | Modificar el array original |
break | break; / break 2; | Salir del bucle (o de N niveles) |
continue | continue; / continue 2; | Saltar a la siguiente vuelta |