Lección 3 de 8: control y arrays en PHP. Un programa casi nunca va en línea recta: decide, repite y guarda datos. Esta lección cubre if, match y los bucles, qué valores cuentan como falsos en PHP, y el tipo de dato que lo sostiene casi todo, el array, con sus funciones y las novedades de PHP 8.4 y 8.5.
Decidir: if, elseif y else
<?php
declare(strict_types=1);
$temperatura = 31;
if ($temperatura >= 30) {
echo "Hace calor\n";
} elseif ($temperatura >= 15) {
echo "Se está bien\n";
} else {
echo "Hace frío\n";
}salidaHace calor
Nada nuevo si vienes de otro lenguaje de la familia de C, salvo que elseif va junto. Lo que sí es propio de PHP es qué acepta como condición: cualquier valor, que se convierte a bool. Y la lista de lo que cuenta como falso tiene una trampa:
<?php
declare(strict_types=1);
$valores = [false, 0, 0.0, "", "0", "0.0", " ", [], [0], null, "false"];
foreach ($valores as $v) {
echo json_encode($v, JSON_PRESERVE_ZERO_FRACTION), " es ", $v ? "verdadero" : "falso", "\n";
}salidafalse es falso 0 es falso 0.0 es falso "" es falso "0" es falso "0.0" es verdadero " " es verdadero [] es falso [0] es verdadero null es falso "false" es verdadero
Son falsos false, el cero, el texto vacío, el array vacío, null… y el texto "0". En cambio "0.0", un espacio o el texto "false" son verdaderos. La trampa es real: si un formulario envía la cantidad "0", if ($cantidad) la trata como si no hubiera llegado nada. Cuando importe, compara de forma explícita: if ($cantidad !== '') o if ($n === 0).
match, para elegir un valor
En la lección 2 vimos que match compara con ===. Además devuelve un valor, admite varios casos por rama y, con match (true), sirve para rangos:
<?php
declare(strict_types=1);
function tipoDeDia(string $dia): string
{
return match ($dia) {
'sábado', 'domingo' => 'fin de semana',
'lunes', 'martes', 'miércoles', 'jueves', 'viernes' => 'laborable',
};
}
function nota(int $puntos): string
{
return match (true) {
$puntos >= 90 => 'sobresaliente',
$puntos >= 70 => 'notable',
$puntos >= 50 => 'aprobado',
default => 'suspenso',
};
}
echo tipoDeDia('domingo'), "\n";
echo nota(75), "\n";
echo nota(30), "\n";salidafin de semana notable suspenso
El error que te ahorra: un caso que se te olvidó
<?php
declare(strict_types=1);
function tipoDeDia(string $dia): string
{
return match ($dia) {
'sábado', 'domingo' => 'fin de semana',
'lunes', 'martes', 'miércoles', 'jueves', 'viernes' => 'laborable',
};
}
echo tipoDeDia('Domingo'), "\n";salida con error
Fatal error: Uncaught UnhandledMatchError: Unhandled match case 'Domingo' in /curso/match_sin_caso.php:6
Stack trace:
#0 /curso/match_sin_caso.php(12): tipoDeDia('Domingo')
#1 {main}
thrown in /curso/match_sin_caso.php on line 6«Domingo» con mayúscula no coincide con ninguna rama. Un switch sin default habría seguido sin hacer nada, y la función habría devuelto un valor vacío que acabaría causando un fallo raro más adelante. match se detiene con un UnhandledMatchError que dice exactamente qué valor no esperaba. Pon default solo cuando de verdad haya un valor razonable para «todo lo demás».
Repetir: for, while y foreach
<?php
declare(strict_types=1);
for ($i = 1; $i <= 3; $i++) {
echo "Vuelta $i\n";
}
$intentos = 0;
while ($intentos < 3) {
$intentos++;
if ($intentos === 2) {
continue;
}
echo "Intento $intentos\n";
}
foreach (['rojo', 'verde', 'azul'] as $color) {
if ($color === 'azul') {
break;
}
echo "Color: $color\n";
}salidaVuelta 1 Vuelta 2 Vuelta 3 Intento 1 Intento 3 Color: rojo Color: verde
for y while son los de siempre; continue salta a la siguiente vuelta y break sale del bucle. Pero en PHP el bucle que más usarás con diferencia es foreach, porque recorre arrays, y casi todo en PHP es un array.
El array de PHP: una lista y un diccionario a la vez
Otros lenguajes tienen listas por un lado y diccionarios por otro. PHP tiene un solo tipo, array, que es un mapa ordenado: cada elemento tiene una clave y un valor, y se recuerdan en el orden en que se añadieron.
<?php
declare(strict_types=1);
$colores = ['rojo', 'verde'];
$colores[] = 'azul';
echo count($colores), " colores; el primero es $colores[0]\n";
$persona = ['nombre' => 'Ana', 'edad' => 31];
$persona['ciudad'] = 'Lima';
foreach ($persona as $clave => $valor) {
echo "$clave: $valor\n";
}
var_dump(array_key_exists('edad', $persona));
var_dump(isset($persona['telefono']));salida3 colores; el primero es rojo nombre: Ana edad: 31 ciudad: Lima bool(true) bool(false)
$colores[] = ... añade al final con la siguiente clave numérica. foreach ($array as $clave => $valor) recorre las dos cosas a la vez. Para saber si una clave existe hay dos formas que no son iguales: isset devuelve false también si la clave existe pero vale null, y array_key_exists solo mira la clave.
Que una lista sea un mapa tiene consecuencias que sorprenden:
<?php declare(strict_types=1); $a = ['1' => 'uno', '01' => 'cero uno', true => 'verdadero', 1.7 => 'uno coma siete']; var_dump($a); $lista = ['a', 'b', 'c']; unset($lista[1]); var_dump($lista); var_dump(array_values($lista));
salida
Deprecated: Implicit conversion from float 1.7 to int loses precision in /curso/claves.php on line 4
array(2) {
[1]=>
string(14) "uno coma siete"
["01"]=>
string(8) "cero uno"
}
array(2) {
[0]=>
string(1) "a"
[2]=>
string(1) "c"
}
array(2) {
[0]=>
string(1) "a"
[1]=>
string(1) "c"
}La clave de texto '1' se convierte en el entero 1, pero '01' se queda como texto. true también se convierte en 1, y 1.7 se trunca a 1 con un aviso de que eso está obsoleto: las tres claves acabaron pisando el mismo elemento, que se queda con el último valor. Y unset quita un elemento sin renumerar: la lista se queda con las claves 0 y 2. Si necesitas una lista seguida otra vez, array_values.
Desestructurar y repartir
<?php
declare(strict_types=1);
[$primero, $segundo] = ['oro', 'plata'];
echo "$primero y $segundo\n";
['nombre' => $nombre, 'edad' => $edad] = ['nombre' => 'Ana', 'edad' => 31];
echo "$nombre tiene $edad años\n";
$a = 1;
$b = 2;
[$a, $b] = [$b, $a];
echo "a = $a, b = $b\n";
$pares = [[1, 'uno'], [2, 'dos']];
foreach ($pares as [$numero, $texto]) {
echo "$numero se escribe $texto\n";
}
$base = ['rojo', 'verde'];
$todos = [...$base, 'azul'];
echo implode(', ', $todos), "\n";salidaoro y plata Ana tiene 31 años a = 2, b = 1 1 se escribe uno 2 se escribe dos rojo, verde, azul
Los corchetes a la izquierda del = reparten un array en variables, también dentro de un foreach, y sirven para intercambiar dos valores sin una variable auxiliar. Los tres puntos (...) despliegan un array dentro de otro.
Funciones de arrays
PHP trae decenas de funciones para arrays. Estas son las que más se usan:
<?php declare(strict_types=1); $precios = ['pan' => 1.20, 'leche' => 0.95, 'café' => 4.50, 'agua' => 0.60]; $conIva = array_map(fn(float $p): float => round($p * 1.16, 2), $precios); $baratos = array_filter($precios, fn(float $p): bool => $p < 1); $total = array_reduce($precios, fn(float $suma, float $p): float => $suma + $p, 0.0); print_r($conIva); print_r($baratos); echo "Total: $total\n"; arsort($precios); echo "El más caro: ", array_key_first($precios), "\n";
salidaArray
(
[pan] => 1.39
[leche] => 1.1
[café] => 5.22
[agua] => 0.7
)
Array
(
[leche] => 0.95
[agua] => 0.6
)
Total: 7.25
El más caro: caféarray_map transforma cada valor, array_filter se queda con los que cumplen una condición y array_reduce los combina en uno. fn(...) => ... es una función flecha, una función corta que veremos en la lección 4. Fíjate en que array_filter conserva las claves, y arsort ordena por valor de mayor a menor manteniéndolas. Las funciones de ordenar cambian el array original y devuelven true, no el array ordenado.
| Para | Función | Ojo |
|---|---|---|
| Ordenar una lista | sort, rsort | renumera las claves |
| Ordenar y conservar claves | asort, arsort, ksort | por valor o por clave |
| Ordenar con tu criterio | usort($a, fn($x, $y) => $x <=> $y) | <=> devuelve -1, 0 o 1 |
| ¿Está este valor? | in_array($v, $a, true) | el true pide comparar con === |
| ¿Dónde está? | array_search($v, $a, true) | puede devolver 0: compara con === false |
| Primer o último elemento | array_first, array_last | nuevas en PHP 8.5 |
| ¿Alguno cumple…? | array_find, array_any, array_all | nuevas en PHP 8.4 |
<?php
declare(strict_types=1);
$letras = ['a', 'b', 'c'];
$pos = array_search('a', $letras, true);
if (!$pos) {
echo "Con !\$pos: no está (¡mentira!)\n";
}
if ($pos !== false) {
echo "Con !== false: está en la posición $pos\n";
}
$numeros = [3, 8, 12, 5];
var_dump(array_find($numeros, fn(int $n): bool => $n > 7));
var_dump(array_any($numeros, fn(int $n): bool => $n > 10));
var_dump(array_all($numeros, fn(int $n): bool => $n > 0));
var_dump(array_first($numeros), array_last($numeros));salidaCon !$pos: no está (¡mentira!) Con !== false: está en la posición 0 int(8) bool(true) bool(true) int(3) int(5)
El primer if es un fallo clásico: array_search encontró la «a» en la posición 0, y 0 es falso. Las funciones nuevas de PHP 8.4 y 8.5 ahorran bucles: array_find devuelve el primer valor que cumple la condición, array_any y array_all dicen si alguno o todos la cumplen.
Callables y el operador tubería
Una función se puede pasar como valor. Desde PHP 8.1, escribir su nombre seguido de (...) crea un callable de primera clase. Y PHP 8.5 añadió el operador tubería |>, que pasa el valor de la izquierda a la función de la derecha, para encadenar pasos en el orden en que se leen:
<?php
declare(strict_types=1);
$longitudes = array_map(strlen(...), ['sol', 'luna', 'estrella']);
print_r($longitudes);
$antes = strtoupper(str_replace(' ', '-', trim(' hola mundo ')));
$despues = ' hola mundo '
|> trim(...)
|> (fn(string $s): string => str_replace(' ', '-', $s))
|> strtoupper(...);
echo $antes, "\n", $despues, "\n";salidaArray
(
[0] => 3
[1] => 4
[2] => 8
)
HOLA-MUNDO
HOLA-MUNDOLas dos líneas hacen lo mismo, pero la primera se lee de dentro hacia fuera y la segunda de arriba abajo. Cada paso de la tubería tiene que ser una función de un solo argumento; cuando necesitas más, como en str_replace, se envuelve en una función flecha entre paréntesis.
Ojo con la seguridad: copiar en tus datos todo lo que llega del formulario
<?php declare(strict_types=1); $usuario = ['nombre' => 'Ana', 'email' => 'ana@ejemplo.com', 'es_admin' => false]; // Lo que envía el formulario de «editar perfil». El atacante añadió un campo. $entrada = ['nombre' => 'Ana María', 'es_admin' => true]; $mal = array_merge($usuario, $entrada); var_dump($mal['es_admin']); $permitidos = ['nombre', 'email']; $bien = array_merge($usuario, array_intersect_key($entrada, array_flip($permitidos))); var_dump($bien['es_admin'], $bien['nombre']);
salidabool(true) bool(false) string(10) "Ana María"
El formulario solo tiene los campos nombre y correo, pero cualquiera puede enviar los campos que quiera. Si mezclas lo que llega con los datos del usuario, el atacante se da permisos de administrador. Se llama asignación masiva, y ha afectado a aplicaciones muy grandes. La defensa es una lista de campos permitidos: array_intersect_key con array_flip se queda solo con esas claves. Lo mismo vale para la función extract(), que convierte las claves de un array en variables: nunca la uses con datos de fuera.
Resumen
| Para | Escribe | Recuerda |
|---|---|---|
| Elegir un valor | match ($x) { ... } | falla si no hay rama; match (true) para rangos |
| Recorrer un array | foreach ($a as $k => $v) | el bucle más usado de PHP |
| Añadir al final | $a[] = $valor; | toma la siguiente clave numérica |
| ¿Existe la clave? | array_key_exists | isset dice false si vale null |
| Repartir en variables | [$a, $b] = $par; | también con claves: ['x' => $x] |
| Transformar o filtrar | array_map, array_filter | array_filter conserva las claves |
| Encadenar pasos | $x |> f(...) |> g(...) | PHP 8.5 |
| Datos de un formulario | lista de campos permitidos | nunca array_merge o extract a ciegas |
Retos
Reto 1. Escribe una función esPar(int $n): string que devuelva «par» o «impar» con match, y úsala con los números del 1 al 4 en un for.
Ver la solución
<?php
declare(strict_types=1);
function esPar(int $n): string
{
return match ($n % 2) {
0 => 'par',
1 => 'impar',
};
}
for ($i = 1; $i <= 4; $i++) {
echo "$i es ", esPar($i), "\n";
}salida1 es impar 2 es par 3 es impar 4 es par
¿Y con números negativos? En PHP, -3 % 2 vale -1, que no está en ninguna rama: match lanzaría un error. Usa abs($n) % 2 si los vas a recibir.
Reto 2. Tienes $notas = ['Ana' => 8, 'Luis' => 4, 'Eva' => 9, 'Raúl' => 5];. Escribe los nombres de los aprobados (5 o más), ordenados de mayor a menor nota.
Ver la solución
<?php
declare(strict_types=1);
$notas = ['Ana' => 8, 'Luis' => 4, 'Eva' => 9, 'Raúl' => 5];
$aprobados = array_filter($notas, fn(int $n): bool => $n >= 5);
arsort($aprobados);
echo implode(', ', array_keys($aprobados)), "\n";salidaEva, Ana, Raúl
array_filter conserva las claves, que aquí son justo los nombres; arsort ordena por nota sin perderlos, y array_keys se queda con ellos.
Reto 3. Cuenta cuántas veces aparece cada palabra en la frase «el sol y el mar y el cielo» y escribe las que salen más de una vez.
Ver la solución
<?php
declare(strict_types=1);
$frase = 'el sol y el mar y el cielo';
$cuentas = [];
foreach (explode(' ', $frase) as $palabra) {
$cuentas[$palabra] = ($cuentas[$palabra] ?? 0) + 1;
}
foreach ($cuentas as $palabra => $veces) {
if ($veces > 1) {
echo "$palabra: $veces\n";
}
}salidael: 3 y: 2
El ?? 0 evita el aviso de clave inexistente la primera vez que aparece cada palabra. PHP también tiene una función que lo hace todo de una vez: array_count_values(explode(' ', $frase)).
Todo el código de esta lección se ejecutó con PHP 8.5.12 en un contenedor limpio antes de publicarla; los mensajes de error y las salidas están copiados de esa ejecución. ¿Algo no te funciona? Revisa que tengas PHP 8.5: php -v.
