Módulo 07: Funciones
Objetivos del módulo
- Comprender qué es una función y por qué es la herramienta básica para organizar código
- Definir funciones con parámetros, valores por defecto y valor de retorno
- Aplicar tipos de parámetros y de retorno con
declare(strict_types=1) - Dominar el paso por referencia, los parámetros variádicos y los argumentos con nombre
- Entender el ámbito de las variables,
globalystatic - Crear funciones anónimas (closures), arrow functions y callables
- Usar la sintaxis first-class callable (
strlen(...)) y funciones de orden superior - Resolver problemas con recursividad
- Encadenar transformaciones con el operador pipe
|>de PHP 8.5 - Proteger valores de retorno importantes con
#[\NoDiscard] - Organizar funciones en varios ficheros con
require_oncey documentarlas con PHPDoc
1. ¿Qué es una función?
📘 Concepto: Una función es un bloque de código con nombre que realiza una tarea concreta. Recibe datos de entrada (parámetros), trabaja con ellos y, opcionalmente, devuelve un resultado. La escribes una vez y la usas todas las veces que quieras.
Piensa en una función como en una receta de cocina: tiene un nombre (“tortilla de patatas”), unos ingredientes (parámetros: huevos, patatas, aceite) y produce un resultado (la tortilla). No necesitas reescribir la receta cada vez que cocinas: la sigues.
Ya has usado muchas funciones nativas de PHP: strlen(), count(), array_map(), str_contains()… En este módulo aprenderás a crear las tuyas.
Beneficios
| Beneficio | Explicación |
|---|---|
| Reutilización | Escribes el código una vez y lo llamas desde muchos sitios |
| Organización | Divides un programa grande en piezas pequeñas con nombre |
| Mantenimiento | Si hay un error, lo corriges en un único lugar |
| Legibilidad | calcularIva($precio) se entiende mejor que la fórmula suelta |
| Pruebas | Una función pequeña se puede probar de forma aislada (módulo 18) |
2. Definir y llamar funciones
Sintaxis básica
<?php
declare(strict_types=1);
function saludar(): void
{
echo "¡Hola desde una función!" . PHP_EOL;
}
saludar(); // ¡Hola desde una función!
saludar(); // Se puede llamar tantas veces como quieras
| Parte | Significado |
|---|---|
function | Palabra reservada que declara una función |
saludar | Nombre de la función (en PHP se usa camelCase por convención) |
() | Lista de parámetros (vacía en este caso) |
: void | Tipo de retorno: void significa “no devuelve nada” |
{ ... } | Cuerpo de la función: el código que se ejecuta |
💡 Consejo: Los nombres de función deberían ser verbos que describan lo que hacen:
calcularTotal(),enviarCorreo(),esPar(),formatearFecha(). Si te cuesta ponerle nombre, probablemente la función hace demasiadas cosas.
Los nombres de función no distinguen mayúsculas
<?php
function despedir(): void
{
echo "Adiós" . PHP_EOL;
}
despedir(); // Adiós
DESPEDIR(); // Adiós (funciona, pero no lo hagas nunca)
⚠️ Importante: A diferencia de las variables (
$nombrey$Nombreson distintas), los nombres de funciones no distinguen mayúsculas y minúsculas. Llámalas siempre exactamente como las declaraste. Además, no puedes declarar dos funciones con el mismo nombre: obtendrásFatal error: Cannot redeclare function.
Puedes llamar a una función antes de declararla
PHP analiza el fichero entero antes de ejecutarlo, así que una función declarada al nivel principal del fichero está disponible desde la primera línea:
<?php
echo doble(21) . PHP_EOL; // 42 (la función está más abajo)
function doble(int $n): int
{
return $n * 2;
}
La excepción son las funciones declaradas dentro de un if o de otra función: solo existen cuando la ejecución pasa por ahí.
3. Parámetros y valor de retorno
📘 Concepto: Los parámetros son las variables que declaras entre paréntesis al definir la función. Los argumentos son los valores concretos que pasas al llamarla.
<?php
declare(strict_types=1);
// parámetros
function sumar(int $a, int $b): int
{
return $a + $b;
}
// argumentos
$resultado = sumar(5, 3);
echo $resultado . PHP_EOL; // 8
echo sumar(10, 20) . PHP_EOL; // 30
return termina la función
return hace dos cosas: devuelve un valor y sale de la función inmediatamente. Todo lo que hay después no se ejecuta:
<?php
declare(strict_types=1);
function clasificarEdad(int $edad): string
{
if ($edad < 0) {
return 'Edad no válida'; // Sale aquí si la edad es negativa
}
if ($edad < 18) {
return 'Menor de edad';
}
return 'Mayor de edad';
}
echo clasificarEdad(15) . PHP_EOL; // Menor de edad
echo clasificarEdad(40) . PHP_EOL; // Mayor de edad
💡 Consejo: Este estilo de salir pronto ante casos especiales se llama cláusulas de guarda (guard clauses). Evita anidar muchos
ify hace el código más fácil de leer.
Devolver varios valores
Una función solo devuelve un valor, pero ese valor puede ser un array. Con la desestructuración (módulo 05) lo recoges cómodamente:
<?php
declare(strict_types=1);
function estadisticas(array $numeros): array
{
return [
'min' => min($numeros),
'max' => max($numeros),
'media' => array_sum($numeros) / count($numeros),
];
}
['min' => $min, 'max' => $max, 'media' => $media] = estadisticas([4, 8, 15, 16, 23, 42]);
echo "Mínimo: $min, Máximo: $max, Media: $media" . PHP_EOL;
// Mínimo: 4, Máximo: 42, Media: 18
Si no hay return
Una función sin return devuelve null. Si su tipo de retorno es void, no debe devolver nada (puede usar return; sin valor para salir antes).
4. Tipos de parámetros y de retorno
Desde PHP 7 puedes (y debes) indicar el tipo de cada parámetro y del valor devuelto. PHP comprobará los tipos en cada llamada.
<?php
declare(strict_types=1);
function precioConIva(float $precio, float $iva = 21.0): float
{
return round($precio * (1 + $iva / 100), 2);
}
echo precioConIva(100.0) . PHP_EOL; // 121
echo precioConIva(50) . PHP_EOL; // 60.5 (int → float sí se permite)
// echo precioConIva("100"); // TypeError con strict_types=1
⚠️ Importante: Sin
declare(strict_types=1);PHP intenta convertir los valores (por ejemplo"100"a100.0). Con modo estricto, pasar un tipo incorrecto lanza unTypeError. La única conversión que se mantiene en modo estricto es deintafloat. Usa siempre el modo estricto: detecta errores antes. La declaración debe ser la primera instrucción del fichero.
Tipos disponibles
| Tipo | Ejemplo | Notas |
|---|---|---|
| Escalares | int, float, string, bool | Los más comunes |
| Compuestos | array, object, callable, iterable | callable no se puede usar en propiedades |
| Clases | DateTime, Producto | Cualquier clase o interfaz |
| Nullable | ?string | Equivale a string\|null |
| Unión | int\|float | Uno de varios tipos (PHP 8.0) |
| Intersección | Countable&Iterator | Debe cumplir todos (PHP 8.1) |
| DNF | (A&B)\|null | Combinación de unión e intersección (PHP 8.2) |
mixed | mixed | Cualquier valor (úsalo poco) |
void | : void | Solo retorno: no devuelve nada |
never | : never | Solo retorno: la función nunca termina normalmente (lanza o hace exit) |
null, true, false | : true | Tipos independientes desde PHP 8.2 |
static, self | : static | Para métodos (módulos 11-12) |
<?php
declare(strict_types=1);
// Tipo unión: acepta int o float
function cuadrado(int|float $n): int|float
{
return $n * $n;
}
// Tipo nullable: puede devolver un string o null
function buscarUsuario(int $id): ?string
{
$usuarios = [1 => 'Ana', 2 => 'Luis'];
return $usuarios[$id] ?? null;
}
// never: la función termina el script siempre
function abortar(string $mensaje): never
{
http_response_code(500);
exit($mensaje);
}
echo cuadrado(3) . PHP_EOL; // 9
echo cuadrado(1.5) . PHP_EOL; // 2.25
echo buscarUsuario(1) ?? 'No existe', PHP_EOL; // Ana
echo buscarUsuario(99) ?? 'No existe', PHP_EOL; // No existe
5. Valores por defecto
Un parámetro puede tener un valor por defecto; si no le pasas argumento, se usa ese valor:
<?php
declare(strict_types=1);
function formatearPrecio(float $cantidad, string $moneda = '€', int $decimales = 2): string
{
return number_format($cantidad, $decimales, ',', '.') . ' ' . $moneda;
}
echo formatearPrecio(1234.5) . PHP_EOL; // 1.234,50 €
echo formatearPrecio(1234.5, '$') . PHP_EOL; // 1.234,50 $
echo formatearPrecio(1234.5, '€', 0) . PHP_EOL; // 1.235 €
⚠️ Importante: Los parámetros con valor por defecto deben ir después de los obligatorios. Declarar
function f($a = 1, $b)está obsoleto (deprecated) desde PHP 8.0, porque el valor por defecto nunca se podría usar.
Los valores por defecto deben ser expresiones constantes: literales, constantes, arrays, null, new Clase() (desde PHP 8.1) y, desde PHP 8.5, también closures estáticas y first-class callables como strlen(...).
<?php
declare(strict_types=1);
// PHP 8.5: un callable como valor por defecto
function transformarTodo(array $textos, Closure $fn = mb_strtoupper(...)): array
{
return array_map($fn, $textos);
}
print_r(transformarTodo(['hola', 'adiós'])); // [HOLA, ADIÓS]
print_r(transformarTodo(['Hola'], mb_strtolower(...))); // [hola]
💡 Consejo: Recuerda del módulo 06 que
strtoupper()no entiende de multibyte:'adiós'se convertiría en'ADIóS'. Para texto en español usa siempre las versionesmb_*.
6. Paso por valor y paso por referencia
Paso por valor (lo habitual)
Por defecto, la función recibe una copia del argumento. Modificar el parámetro dentro no afecta a la variable original:
<?php
declare(strict_types=1);
function duplicar(int $numero): void
{
$numero *= 2;
echo "Dentro: $numero" . PHP_EOL; // Dentro: 20
}
$x = 10;
duplicar($x);
echo "Fuera: $x" . PHP_EOL; // Fuera: 10 (no ha cambiado)
Esto también ocurre con los arrays: se pasan por valor (PHP usa copy-on-write, así que solo se copian realmente si los modificas).
Paso por referencia con &
Si antepones & al parámetro, la función trabaja con la variable original:
<?php
declare(strict_types=1);
function duplicarRef(int &$numero): void
{
$numero *= 2;
}
$x = 10;
duplicarRef($x);
echo $x . PHP_EOL; // 20 (ahora sí ha cambiado)
function anadirEtiqueta(array &$etiquetas, string $nueva): void
{
if (!in_array($nueva, $etiquetas, true)) {
$etiquetas[] = $nueva;
}
}
$tags = ['php', 'web'];
anadirEtiqueta($tags, 'backend');
anadirEtiqueta($tags, 'php'); // No se repite
print_r($tags); // [php, web, backend]
Funciones nativas como sort(), shuffle(), array_push() o preg_match() (su tercer parámetro) usan paso por referencia: por eso sort($array) modifica el array y devuelve un bool.
⚠️ Importante: Solo puedes pasar variables a un parámetro por referencia.
duplicarRef(10)produce un error, porque un literal no es una variable que se pueda modificar.
💡 Consejo: Abusar de las referencias hace el código difícil de seguir (“¿quién ha cambiado esta variable?”). Prefiere devolver el nuevo valor:
$tags = anadirEtiqueta($tags, 'php');. Usa&solo cuando tenga una ventaja clara.
7. Parámetros variádicos (...)
Un parámetro variádico recoge cualquier número de argumentos en un array. Se escribe con ... delante del nombre y debe ser el último:
<?php
declare(strict_types=1);
function sumarTodos(int ...$numeros): int
{
// $numeros es un array con todos los argumentos
return array_sum($numeros);
}
echo sumarTodos() . PHP_EOL; // 0
echo sumarTodos(1, 2) . PHP_EOL; // 3
echo sumarTodos(1, 2, 3, 4, 5) . PHP_EOL; // 15
function etiquetaHtml(string $etiqueta, string ...$clases): string
{
$atributo = $clases ? ' class="' . implode(' ', $clases) . '"' : '';
return "<$etiqueta$atributo></$etiqueta>";
}
echo etiquetaHtml('div') . PHP_EOL; // <div></div>
echo etiquetaHtml('div', 'card', 'shadow') . PHP_EOL; // <div class="card shadow"></div>
El operador spread al llamar
El mismo ... en la llamada hace lo contrario: despliega un array en argumentos sueltos:
<?php
declare(strict_types=1);
function sumarTres(int $a, int $b, int $c): int
{
return $a + $b + $c;
}
$valores = [1, 2, 3];
echo sumarTres(...$valores) . PHP_EOL; // 6
// Con claves de texto, se convierten en argumentos con nombre (PHP 8.1)
$args = ['c' => 30, 'a' => 10, 'b' => 20];
echo sumarTres(...$args) . PHP_EOL; // 60
8. Argumentos con nombre (named arguments)
Desde PHP 8.0 puedes pasar argumentos indicando el nombre del parámetro. Esto permite cambiar el orden, saltarte opcionales y, sobre todo, hace las llamadas autoexplicativas.
<?php
declare(strict_types=1);
function crearUsuario(string $nombre, int $edad, string $ciudad = 'Madrid', bool $activo = true): string
{
$estado = $activo ? 'activo' : 'inactivo';
return "$nombre ($edad años, $ciudad) - $estado";
}
// Por posición
echo crearUsuario('Ana', 25, 'Sevilla') . PHP_EOL;
// Con nombre: el orden da igual
echo crearUsuario(edad: 30, nombre: 'Luis') . PHP_EOL;
// Te saltas $ciudad y cambias solo $activo
echo crearUsuario('Marta', 41, activo: false) . PHP_EOL;
// Marta (41 años, Madrid) - inactivo
Brillan especialmente con funciones nativas de muchos parámetros:
<?php
// ¿Qué significan esos argumentos?
echo htmlspecialchars($texto, ENT_QUOTES, 'UTF-8', false);
// Mucho más claro
echo htmlspecialchars($texto, double_encode: false);
// array_fill con nombre
$ceros = array_fill(start_index: 0, count: 5, value: 0);
// json_encode con flags
echo json_encode(['ñ' => 'año'], flags: JSON_UNESCAPED_UNICODE);
| Regla | Ejemplo |
|---|---|
| Los posicionales van antes que los nombrados | f(1, b: 2) ✅ — f(a: 1, 2) ❌ |
| No puedes repetir un parámetro | f(1, a: 2) ❌ Error |
El nombre es el del parámetro, sin $ | crearUsuario(nombre: 'Ana', ...) |
| Con variádicos, los nombres extra van con clave de texto | f(...$args) |
⚠️ Importante: Con los argumentos con nombre, el nombre de los parámetros pasa a formar parte de la API de tu función. Si renombras
$ciudada$localidad, romperás el código de quien llamaba conciudad:. Elige buenos nombres desde el principio.
9. Funciones nativas útiles
PHP incluye miles de funciones. Antes de escribir la tuya, comprueba si ya existe. Aquí tienes una selección de las que más usarás (las de cadenas y arrays las viste en los módulos 05 y 06):
| Categoría | Funciones |
|---|---|
| Matemáticas | abs(), round(), floor(), ceil(), max(), min(), intdiv(), fdiv(), pow() / **, sqrt() |
| Aleatorios | random_int() (segura), mt_rand(), array_rand(), shuffle() |
| Tipos | gettype(), get_debug_type(), is_int(), is_string(), is_array(), is_numeric(), is_callable() |
| Arrays (8.4) | array_find(), array_find_key(), array_any(), array_all() |
| Arrays (8.5) | array_first(), array_last() |
| Fecha | time(), date(), mktime(), checkdate(), strtotime() |
| Funciones | function_exists(), func_get_args(), call_user_func(), call_user_func_array() |
| Depuración | var_dump(), print_r(), var_export(), debug_print_backtrace() |
<?php
declare(strict_types=1);
$pedidos = [
['id' => 1, 'total' => 25.0, 'pagado' => true],
['id' => 2, 'total' => 120.5, 'pagado' => false],
['id' => 3, 'total' => 60.0, 'pagado' => true],
];
// PHP 8.4: primer elemento que cumple la condición
$grande = array_find($pedidos, fn(array $p) => $p['total'] > 100);
echo $grande['id'] . PHP_EOL; // 2
// PHP 8.4: ¿alguno / todos cumplen?
var_dump(array_any($pedidos, fn(array $p) => !$p['pagado'])); // bool(true)
var_dump(array_all($pedidos, fn(array $p) => $p['total'] > 10)); // bool(true)
// PHP 8.5: primer y último valor sin tocar el puntero interno
echo array_first($pedidos)['id'] . PHP_EOL; // 1
echo array_last($pedidos)['id'] . PHP_EOL; // 3
echo random_int(1, 6) . PHP_EOL; // Dado: 1..6
echo intdiv(17, 5) . PHP_EOL; // 3
echo get_debug_type(3.14) . PHP_EOL; // float
var_dump(function_exists('array_first')); // bool(true) en PHP 8.5
💡 Consejo: El manual oficial (
https://www.php.net/manual/es/) es tu mejor amigo. Escribe en el buscadorphp.net/nombre_funcion(por ejemplophp.net/array_find) y llegarás directamente a su ficha.
10. Ámbito de las variables: global y static
📘 Concepto: El ámbito (scope) es la zona del código donde una variable existe. En PHP, cada función tiene su propio ámbito aislado: no ve las variables de fuera, y las suyas desaparecen al terminar.
<?php
$mensaje = 'Hola';
function mostrar(): void
{
// echo $mensaje; // Warning: Undefined variable $mensaje
$interna = 'Solo existo aquí';
echo $interna . PHP_EOL;
}
mostrar();
// echo $interna; // Warning: Undefined variable $interna
⚠️ Importante: Esto sorprende a quien viene de JavaScript, donde una función sí ve las variables exteriores. En PHP, una función normal solo ve sus parámetros, sus variables locales y las superglobales (
$_GET,$_POST…).
global (y por qué evitarlo)
La palabra global importa una variable del ámbito global:
<?php
$contador = 0;
function incrementar(): void
{
global $contador; // Ahora $contador es la variable global
$contador++;
}
incrementar();
incrementar();
echo $contador . PHP_EOL; // 2
Funciona, pero crea dependencias ocultas: mirando la firma incrementar(): void nadie sabe que modifica algo externo. Es difícil de probar y de depurar. La alternativa limpia es pasar el dato como parámetro y devolver el resultado:
<?php
declare(strict_types=1);
function incrementar(int $contador): int
{
return $contador + 1;
}
$contador = 0;
$contador = incrementar($contador);
Variables static
Una variable static dentro de una función conserva su valor entre llamadas. Se inicializa solo la primera vez:
<?php
declare(strict_types=1);
function siguienteId(): int
{
static $id = 0; // Solo se ejecuta en la primera llamada
return ++$id;
}
echo siguienteId() . PHP_EOL; // 1
echo siguienteId() . PHP_EOL; // 2
echo siguienteId() . PHP_EOL; // 3
Un uso práctico es la caché (memoización) de cálculos costosos:
<?php
declare(strict_types=1);
function configuracion(): array
{
static $config = null;
if ($config === null) {
echo "(leyendo configuración...)" . PHP_EOL;
$config = ['app' => 'Tienda', 'debug' => true]; // Imagina que viene de un fichero
}
return $config;
}
configuracion(); // (leyendo configuración...)
echo configuracion()['app'] . PHP_EOL; // Tienda (sin volver a leer)
11. Funciones anónimas (closures)
📘 Concepto: Una función anónima es una función sin nombre que se puede guardar en una variable, pasar como argumento o devolver desde otra función. En PHP son objetos de la clase
Closure.
<?php
declare(strict_types=1);
$saludar = function (string $nombre): string {
return "Hola, $nombre";
}; // ← Ojo: lleva punto y coma, es una asignación
echo $saludar('Ana') . PHP_EOL; // Hola, Ana
echo get_debug_type($saludar) . PHP_EOL; // Closure
Su uso más habitual es como argumento de otra función:
<?php
declare(strict_types=1);
$precios = [10, 25, 7, 100];
$conIva = array_map(function (int $p): float {
return $p * 1.21;
}, $precios);
print_r($conIva); // [12.1, 30.25, 8.47, 121]
Capturar variables con use
Igual que las funciones normales, una closure no ve las variables exteriores… salvo que las captures con use:
<?php
declare(strict_types=1);
$iva = 21;
$aplicarIva = function (float $precio) use ($iva): float {
return $precio * (1 + $iva / 100);
};
echo $aplicarIva(100) . PHP_EOL; // 121
Captura por valor (por defecto)
use ($iva) copia el valor en el momento de crear la closure. Si después cambias $iva, la closure no se entera:
<?php
$iva = 21;
$calcular = function (float $p) use ($iva): float {
return $p * (1 + $iva / 100);
};
$iva = 10; // Cambio posterior
echo $calcular(100) . PHP_EOL; // 121 (sigue usando 21)
Captura por referencia con &
Con use (&$variable) la closure comparte la variable original: ve los cambios y puede modificarla.
<?php
declare(strict_types=1);
$total = 0;
$sumarAlTotal = function (int $cantidad) use (&$total): void {
$total += $cantidad;
};
$sumarAlTotal(10);
$sumarAlTotal(5);
echo $total . PHP_EOL; // 15
// Ejemplo práctico: contar elementos procesados dentro de array_walk
$procesados = 0;
$nombres = ['ana', 'luis', 'marta'];
array_walk($nombres, function (string &$n) use (&$procesados): void {
$n = ucfirst($n);
$procesados++;
});
print_r($nombres); // [Ana, Luis, Marta]
echo $procesados . PHP_EOL; // 3
| Captura | Sintaxis | ¿Ve cambios posteriores? | ¿Puede modificar la original? |
|---|---|---|---|
| Por valor | use ($x) | No | No |
| Por referencia | use (&$x) | Sí | Sí |
Funciones que devuelven funciones
Una closure puede “recordar” datos: es una forma elegante de crear funciones configurables (fábricas):
<?php
declare(strict_types=1);
function crearMultiplicador(int $factor): Closure
{
return function (int $n) use ($factor): int {
return $n * $factor;
};
}
$doble = crearMultiplicador(2);
$triple = crearMultiplicador(3);
echo $doble(5) . PHP_EOL; // 10
echo $triple(5) . PHP_EOL; // 15
Closures estáticas
Si una closure se crea dentro de una clase, captura automáticamente $this. Declararla con static function evita esa captura (ligeramente más eficiente y obligatorio en expresiones constantes de PHP 8.5). Lo verás en los módulos de POO.
12. Arrow functions (fn() =>)
PHP 7.4 introdujo una sintaxis corta para closures de una sola expresión:
<?php
declare(strict_types=1);
// Closure clásica
$cuadrado = function (int $n): int {
return $n * $n;
};
// Arrow function equivalente
$cuadrado = fn(int $n): int => $n * $n;
echo $cuadrado(4) . PHP_EOL; // 16
Captura automática por valor
La gran diferencia: una arrow function captura automáticamente (por valor) todas las variables exteriores que use. No necesitas use:
<?php
declare(strict_types=1);
$iva = 21;
$descuento = 0.10;
$precioFinal = fn(float $p): float => $p * (1 - $descuento) * (1 + $iva / 100);
echo $precioFinal(100) . PHP_EOL; // 108.9
// Ideal para array_map / array_filter
$productos = [
['nombre' => 'Teclado', 'precio' => 45],
['nombre' => 'Monitor', 'precio' => 180],
['nombre' => 'Ratón', 'precio' => 20],
];
$limite = 50;
$baratos = array_filter($productos, fn(array $p) => $p['precio'] < $limite);
$nombres = array_map(fn(array $p) => $p['nombre'], $baratos);
print_r($nombres); // [0 => Teclado, 2 => Ratón]
| Característica | function () use () | fn() => |
|---|---|---|
| Cuerpo | Bloque con varias instrucciones | Una sola expresión |
return | Explícito | Implícito |
| Captura de variables | Manual con use | Automática |
| Captura por referencia | Sí, use (&$x) | No (siempre por valor) |
| Uso típico | Lógica compleja, modificar estado | Transformaciones y filtros cortos |
⚠️ Importante: Una arrow function no puede modificar las variables exteriores: trabaja sobre una copia.
fn() => $total++no cambia$totalfuera. Si necesitas modificar, usafunction () use (&$total).
13. Callables y sintaxis first-class callable
📘 Concepto: Un callable es cualquier cosa que PHP puede “llamar” como una función. El tipo
callableacepta todas estas formas.
| Forma | Ejemplo |
|---|---|
| Nombre de función como string | 'strtoupper' |
| Closure o arrow function | fn($x) => $x * 2 |
| Método de objeto | [$objeto, 'metodo'] |
| Método estático | [Clase::class, 'metodo'] o 'Clase::metodo' |
| Objeto invocable | Un objeto con método __invoke() (módulo 12) |
| First-class callable (PHP 8.1) | strtoupper(...), $obj->metodo(...), Clase::metodo(...) |
First-class callable syntax: funcion(...)
Escribir el nombre de una función seguido de (...) (tres puntos literales, no es una omisión) no la llama: crea un objeto Closure que apunta a ella.
<?php
declare(strict_types=1);
$longitud = strlen(...); // Closure que envuelve strlen
echo $longitud('PHP 8.5') . PHP_EOL; // 7
$palabras = [' hola ', ' mundo '];
print_r(array_map(trim(...), $palabras)); // [hola, mundo]
// Con funciones propias
function esPar(int $n): bool
{
return $n % 2 === 0;
}
print_r(array_filter([1, 2, 3, 4, 5, 6], esPar(...))); // [2, 4, 6]
Ventajas frente al string 'trim':
- El IDE y los analizadores estáticos (PHPStan) entienden que es una referencia a la función: autocompletado, renombrado y detección de errores.
- Si la función no existe, el error aparece al crear el callable, no más tarde.
- Funciona con métodos sin la incómoda sintaxis de array
[$this, 'metodo'].
💡 Consejo: En código moderno, usa siempre
funcion(...)en lugar de'funcion'como string. Es la forma que verás también con el operador pipe.
Recibir un callable como parámetro
<?php
declare(strict_types=1);
function aplicarDosVeces(callable $fn, mixed $valor): mixed
{
return $fn($fn($valor));
}
echo aplicarDosVeces(fn(int $n) => $n + 10, 1) . PHP_EOL; // 21
echo aplicarDosVeces(strrev(...), 'PHP') . PHP_EOL; // PHP
Usa callable para parámetros; si quieres algo más específico o guardarlo en una propiedad de clase, usa Closure.
14. Funciones de orden superior
📘 Concepto: Una función de orden superior es la que recibe funciones como argumento o devuelve una función.
array_map,array_filter,usortyarray_reduceson funciones de orden superior.
Las cuatro grandes
<?php
declare(strict_types=1);
$ventas = [
['producto' => 'Café', 'unidades' => 3, 'precio' => 1.5],
['producto' => 'Té', 'unidades' => 1, 'precio' => 1.2],
['producto' => 'Tarta', 'unidades' => 2, 'precio' => 3.8],
];
// array_map: transforma cada elemento
$importes = array_map(fn(array $v) => $v['unidades'] * $v['precio'], $ventas);
// [4.5, 1.2, 7.6]
// array_filter: se queda con los que cumplen
$grandes = array_filter($importes, fn(float $i) => $i > 2);
// [0 => 4.5, 2 => 7.6]
// array_reduce: reduce todo a un valor
$total = array_reduce($importes, fn(float $acc, float $i) => $acc + $i, 0.0);
echo "Total: $total" . PHP_EOL; // Total: 13.3
// usort: ordena con un criterio propio (nave espacial <=>)
usort($ventas, fn(array $a, array $b) => $b['precio'] <=> $a['precio']);
echo $ventas[0]['producto'] . PHP_EOL; // Tarta
Crear tus propias funciones de orden superior
<?php
declare(strict_types=1);
/** Ejecuta una función y mide cuánto tarda */
function cronometrar(callable $tarea): mixed
{
$inicio = hrtime(true);
$resultado = $tarea();
$ms = (hrtime(true) - $inicio) / 1_000_000;
echo sprintf("Tardó %.3f ms", $ms) . PHP_EOL;
return $resultado;
}
$suma = cronometrar(fn() => array_sum(range(1, 1_000_000)));
echo $suma . PHP_EOL; // 500000500000
/** Devuelve una versión de $fn que guarda resultados ya calculados */
function memoizar(callable $fn): Closure
{
$cache = [];
return function (mixed ...$args) use ($fn, &$cache): mixed {
$clave = serialize($args);
return $cache[$clave] ??= $fn(...$args);
};
}
$lenta = function (int $n): int {
usleep(200_000); // Simula 0,2 s de trabajo
return $n * $n;
};
$rapida = memoizar($lenta);
cronometrar(fn() => $rapida(9)); // ~200 ms
cronometrar(fn() => $rapida(9)); // ~0 ms (desde la caché)
Componer funciones
Componer es crear una función nueva que aplica otras en cadena. Antes del operador pipe era la forma de expresar transformaciones encadenadas:
<?php
declare(strict_types=1);
function componer(callable ...$fns): Closure
{
return fn(mixed $valor) => array_reduce($fns, fn($acc, $fn) => $fn($acc), $valor);
}
$limpiar = componer(trim(...), mb_strtolower(...), ucfirst(...));
echo $limpiar(' HOLA MUNDO ') . PHP_EOL; // Hola mundo
15. Recursividad
📘 Concepto: Una función recursiva es aquella que se llama a sí misma. Divide un problema en una versión más pequeña del mismo problema. Siempre necesita un caso base que detenga las llamadas.
Analogía: las muñecas rusas (matrioskas). Para llegar a la más pequeña, abres una y dentro hay otra igual pero más pequeña… hasta que llegas a la que ya no se abre (caso base).
Factorial
<?php
declare(strict_types=1);
function factorial(int $n): int
{
if ($n <= 1) { // Caso base
return 1;
}
return $n * factorial($n - 1); // Caso recursivo
}
echo factorial(5) . PHP_EOL; // 120
factorial(5)
→ 5 * factorial(4)
→ 4 * factorial(3)
→ 3 * factorial(2)
→ 2 * factorial(1)
→ 1 (caso base)
→ 2 * 1 = 2
→ 3 * 2 = 6
→ 4 * 6 = 24
→ 5 * 24 = 120
Recorrer estructuras anidadas
La recursividad brilla con datos con forma de árbol: menús, categorías, carpetas, comentarios con respuestas…
<?php
declare(strict_types=1);
$menu = [
['titulo' => 'Inicio', 'url' => '/'],
['titulo' => 'Productos', 'url' => '/productos', 'hijos' => [
['titulo' => 'Portátiles', 'url' => '/productos/portatiles'],
['titulo' => 'Accesorios', 'url' => '/productos/accesorios', 'hijos' => [
['titulo' => 'Ratones', 'url' => '/productos/accesorios/ratones'],
]],
]],
['titulo' => 'Contacto', 'url' => '/contacto'],
];
function pintarMenu(array $items, int $nivel = 0): string
{
$sangria = str_repeat(' ', $nivel);
$html = "$sangria<ul>\n";
foreach ($items as $item) {
$html .= "$sangria <li><a href=\"{$item['url']}\">{$item['titulo']}</a>";
if (!empty($item['hijos'])) {
$html .= "\n" . pintarMenu($item['hijos'], $nivel + 2) . "$sangria ";
}
$html .= "</li>\n";
}
return $html . "$sangria</ul>\n";
}
echo pintarMenu($menu);
/** Suma todos los números de un array con cualquier profundidad */
function sumaProfunda(array $datos): int|float
{
$total = 0;
foreach ($datos as $valor) {
$total += is_array($valor) ? sumaProfunda($valor) : $valor;
}
return $total;
}
echo sumaProfunda([1, [2, 3], [4, [5, [6]]]]) . PHP_EOL; // 21
Fibonacci y el coste de la recursividad ingenua
<?php
declare(strict_types=1);
// Versión ingenua: recalcula lo mismo miles de veces (exponencial)
function fib(int $n): int
{
return $n < 2 ? $n : fib($n - 1) + fib($n - 2);
}
// Versión con memoización usando una variable static
function fibMemo(int $n): int
{
static $memo = [0 => 0, 1 => 1];
return $memo[$n] ??= fibMemo($n - 1) + fibMemo($n - 2);
}
echo fib(25) . PHP_EOL; // 75025 (ya tarda algo)
echo fibMemo(90) . PHP_EOL; // 2880067194370816120 (instantáneo)
⚠️ Importante: Cada llamada recursiva ocupa memoria en la pila. Una recursión sin caso base (o muy profunda) agota la memoria y termina con un error fatal. Si un bucle resuelve el problema de forma sencilla, suele ser mejor opción.
16. El operador pipe |> (PHP 8.5)
El problema: funciones anidadas
Imagina que quieres convertir el título " Curso de PHP 8.5 " en un slug para una URL: curso-de-php-85. Con funciones anidadas el código se lee de dentro hacia fuera, al revés de como ocurre:
<?php
$titulo = ' Curso de PHP 8.5 ';
$slug = strtolower(str_replace('.', '', str_replace(' ', '-', trim($titulo))));
La alternativa clásica son variables intermedias, que se leen bien pero ensucian el código:
<?php
$paso1 = trim($titulo);
$paso2 = str_replace(' ', '-', $paso1);
$paso3 = str_replace('.', '', $paso2);
$slug = strtolower($paso3);
La solución: |>
📘 Concepto: El operador pipe
|>toma el valor de su izquierda y se lo pasa como único argumento al callable de su derecha.$x |> f(...)equivale af($x). Encadenando varios, los datos “fluyen” de izquierda a derecha, como en una cadena de montaje.
<?php
declare(strict_types=1);
$titulo = ' Curso de PHP 8.5 ';
$slug = $titulo
|> trim(...)
|> (fn(string $s) => str_replace(' ', '-', $s))
|> (fn(string $s) => str_replace('.', '', $s))
|> strtolower(...);
var_dump($slug); // string(15) "curso-de-php-85"
flowchart LR
A["' Curso de PHP 8.5 '"] -->|trim| B["'Curso de PHP 8.5'"]
B -->|espacios a guiones| C["'Curso-de-PHP-8.5'"]
C -->|quitar puntos| D["'Curso-de-PHP-85'"]
D -->|strtolower| E["'curso-de-php-85'"]
Las reglas del pipe
| Regla | Explicación |
|---|---|
| El lado derecho es un callable | trim(...), una closure, $obj->metodo(...), una variable con una closure… |
| Debe aceptar un solo parámetro obligatorio | El valor de la izquierda es ese único argumento |
| Las arrow functions van entre paréntesis | \|> (fn($x) => ...); sin paréntesis la arrow function “se tragaría” el resto de la cadena |
| No admite parámetros por referencia | $arr \|> sort(...) no es válido |
| Se evalúa de izquierda a derecha | Asociativo a la izquierda |
| Precedencia | Se aplica después de las operaciones aritméticas y antes de las comparaciones y de ?? |
Con funciones nativas de un parámetro
Muchas funciones nativas encajan directamente porque reciben un único argumento: trim, strtolower, strtoupper, ucfirst, strrev, strlen, count, array_unique, array_values, array_sum, json_encode…
<?php
declare(strict_types=1);
$numeros = [5, 3, 5, 8, 3, 1];
$suma = $numeros
|> array_unique(...) // [5, 3, 8, 1]
|> array_sum(...); // 17
echo $suma . PHP_EOL; // 17
// Funciones con parámetros opcionales también valen (solo se pasa el primero)
echo ' hola ' |> trim(...) |> strlen(...), PHP_EOL; // 4
Con funciones de varios parámetros
Si la función necesita más argumentos (como str_replace o array_map), envuélvela en una arrow function entre paréntesis que reciba el valor:
<?php
declare(strict_types=1);
$csv = ' ana , LUIS, marta ,ana ';
$nombres = $csv
|> (fn(string $s) => explode(',', $s))
|> (fn(array $a) => array_map(trim(...), $a))
|> (fn(array $a) => array_map(mb_strtolower(...), $a))
|> array_unique(...)
|> (fn(array $a) => array_map(ucfirst(...), $a))
|> (fn(array $a) => implode(', ', $a));
echo $nombres . PHP_EOL; // Ana, Luis, Marta
Con funciones propias
Las funciones propias de un parámetro son las que mejor encajan con el pipe. Esto te anima a escribir funciones pequeñas y con un único propósito:
<?php
declare(strict_types=1);
function quitarTildes(string $s): string
{
return strtr($s, ['á' => 'a', 'é' => 'e', 'í' => 'i', 'ó' => 'o', 'ú' => 'u', 'ñ' => 'n', 'ü' => 'u']);
}
function soloAlfanumerico(string $s): string
{
return preg_replace('/[^a-z0-9]+/', '-', $s);
}
function recortarGuiones(string $s): string
{
return trim($s, '-');
}
function slugify(string $texto): string
{
return $texto
|> trim(...)
|> mb_strtolower(...)
|> quitarTildes(...)
|> soloAlfanumerico(...)
|> recortarGuiones(...);
}
echo slugify(' ¡Programación en PHP 8.5: más fácil! ') . PHP_EOL;
// programacion-en-php-8-5-mas-facil
También puedes usar una fábrica de funciones (sección 11) para crear callables de un parámetro “a medida”:
<?php
declare(strict_types=1);
function multiplicarPor(int $factor): Closure
{
return fn(int $n): int => $n * $factor;
}
function sumar(int $cantidad): Closure
{
return fn(int $n): int => $n + $cantidad;
}
echo 5 |> multiplicarPor(3) |> sumar(1), PHP_EOL; // 16
Fíjate: aquí multiplicarPor(3) sí se llama (no lleva ...), y lo que devuelve es la closure que recibe el valor.
Precedencia en la práctica
<?php
declare(strict_types=1);
// La aritmética se evalúa antes: equivale a (5 + 2) |> ...
echo 5 + 2 |> (fn(int $n) => $n * 10), PHP_EOL; // 70
// La comparación se evalúa después: compara el resultado del pipe
var_dump('beep' |> strlen(...) === 4); // bool(true)
// ?? se aplica al resultado de toda la cadena
function buscarNombre(int $id): ?string
{
return [1 => 'Ana'][$id] ?? null;
}
echo 7 |> buscarNombre(...) ?? 'Invitado', PHP_EOL; // Invitado
💡 Consejo: Aunque la precedencia esté bien definida, cuando mezcles pipe con otros operadores usa paréntesis para que la intención sea obvia a cualquiera que lea el código.
Comparativa: anidamiento frente a pipe
| Aspecto | Anidamiento f(g(h($x))) | Variables intermedias | Pipe $x \|> h(...) \|> g(...) \|> f(...) |
|---|---|---|---|
| Orden de lectura | De dentro hacia fuera | De arriba abajo | De izquierda a derecha / arriba abajo |
| Variables extra | No | Sí, una por paso | No |
| Añadir/quitar un paso | Difícil (paréntesis) | Fácil | Muy fácil (una línea) |
| Funciones con varios parámetros | Directo | Directo | Necesitan envolverse en fn |
| Versión de PHP | Todas | Todas | 8.5+ |
⚠️ Importante: No conviertas todo en pipes. Para una o dos funciones,
strtoupper(trim($x))sigue siendo perfectamente legible. El pipe compensa en cadenas de tres o más transformaciones, especialmente al procesar datos.
17. El atributo #[\NoDiscard] (PHP 8.5)
Algunas funciones devuelven un valor que nunca debería ignorarse: el resultado de una validación, un objeto nuevo (en vez de modificar el original), un código de error… Ignorarlo casi siempre es un error.
📘 Concepto: Al marcar una función con el atributo
#[\NoDiscard], PHP emite un aviso (warning) si alguien la llama sin usar su valor de retorno. Opcionalmente puedes añadir un mensaje que explique por qué.
<?php
declare(strict_types=1);
#[\NoDiscard("porque contiene los errores de validación")]
function validarRegistro(array $datos): array
{
$errores = [];
if (($datos['email'] ?? '') === '') {
$errores['email'] = 'El email es obligatorio';
}
return $errores;
}
validarRegistro(['email' => '']);
// Warning: The return value of function validarRegistro() should either be used
// or intentionally ignored by casting it as (void), porque contiene los errores de validación
$errores = validarRegistro(['email' => '']); // ✅ Sin aviso: el valor se usa
Ignorarlo a propósito: (void)
Si de verdad quieres descartar el valor, PHP 8.5 añade el cast (void) para dejar claro que es intencionado:
<?php
(void) validarRegistro($datos); // Sin aviso: "sé lo que hago"
| Detalle | Comportamiento |
|---|---|
| Dónde se aplica | Funciones y métodos |
| No se permite en | Funciones con retorno : void o : never (no tendría sentido) |
| Tipo de aviso | E_USER_WARNING en funciones propias |
(void) | Es una instrucción, no una expresión: no puede ir dentro de otra expresión |
| Compatibilidad | En PHP < 8.5 el atributo simplemente se ignora |
💡 Consejo: Úsalo con moderación, en funciones donde ignorar el resultado sea un fallo real (validaciones, métodos
with*de objetos inmutables que verás en el módulo 11). Si lo pones en todas partes, la gente acabará llenando el código de(void).
18. Organizar funciones en ficheros: include y require
Cuando tu proyecto crece, no quieres todas las funciones en un solo fichero. PHP permite incluir el contenido de otro fichero:
mi-proyecto/
├── index.php
└── lib/
├── cadenas.php
└── precios.php
lib/precios.php
<?php
declare(strict_types=1);
function precioConIva(float $precio, float $iva = 21.0): float
{
return round($precio * (1 + $iva / 100), 2);
}
function formatearEuros(float $cantidad): string
{
return number_format($cantidad, 2, ',', '.') . ' €';
}
index.php
<?php
declare(strict_types=1);
require_once __DIR__ . '/lib/precios.php';
require_once __DIR__ . '/lib/cadenas.php';
echo formatearEuros(precioConIva(99.99)) . PHP_EOL; // 120,99 €
Las cuatro variantes
| Instrucción | Si el fichero no existe | Si ya se incluyó antes |
|---|---|---|
include | Warning y el script continúa | Lo vuelve a incluir |
require | Error fatal y el script se detiene | Lo vuelve a incluir |
include_once | Warning y continúa | No hace nada |
require_once | Error fatal | No hace nada |
⚠️ Importante: Para ficheros con funciones usa
require_once: si el fichero se incluyera dos veces, PHP intentaría redeclarar las funciones y fallaría conCannot redeclare function.includetiene sentido para fragmentos opcionales, como plantillas de vista.
💡 Consejo: Usa siempre rutas absolutas con
__DIR__(la carpeta del fichero actual). Una ruta relativa como'lib/precios.php'depende del directorio desde el que se ejecute el script y falla de forma imprevisible.
include también devuelve valores
Un fichero incluido puede terminar con return, lo que se usa mucho para ficheros de configuración:
<?php
// config.php
return [
'app_nombre' => 'Mi tienda',
'iva' => 21,
];
<?php
// index.php
$config = require __DIR__ . '/config.php';
echo $config['app_nombre'] . PHP_EOL; // Mi tienda
En el módulo 13 verás que con clases y Composer no necesitarás escribir require para cada fichero: el autoload los cargará automáticamente.
19. Documentar funciones con PHPDoc
PHPDoc es el estándar de comentarios de documentación en PHP. Empieza con /** y usa etiquetas con @. Los IDE (VS Code con Intelephense, PhpStorm) y herramientas como PHPStan lo leen para darte ayuda y detectar errores.
<?php
declare(strict_types=1);
/**
* Calcula el precio final de un producto aplicando descuento e IVA.
*
* El descuento se aplica antes del impuesto.
*
* @param float $precio Precio base sin impuestos, en euros.
* @param float $descuento Porcentaje de descuento entre 0 y 100.
* @param float $iva Porcentaje de IVA (21 por defecto).
*
* @return float Precio final redondeado a 2 decimales.
*
* @throws InvalidArgumentException Si el descuento está fuera de rango.
*
* @example precioFinal(100, 10) // 108.9
*/
function precioFinal(float $precio, float $descuento = 0, float $iva = 21.0): float
{
if ($descuento < 0 || $descuento > 100) {
throw new InvalidArgumentException('El descuento debe estar entre 0 y 100');
}
return round($precio * (1 - $descuento / 100) * (1 + $iva / 100), 2);
}
PHPDoc completa lo que los tipos no pueden expresar
El tipo array de PHP no dice qué contiene el array. PHPDoc sí:
<?php
declare(strict_types=1);
/**
* @param list<string> $nombres Lista de nombres (índices 0, 1, 2...)
* @param array<string, int> $edades Mapa nombre => edad
* @return array{media: float, total: int} Array con forma conocida
*/
function resumen(array $nombres, array $edades): array
{
$total = count($nombres);
return ['media' => array_sum($edades) / max($total, 1), 'total' => $total];
}
| Etiqueta | Uso |
|---|---|
@param tipo $nombre descripción | Describe un parámetro |
@return tipo descripción | Describe el valor devuelto |
@throws Clase | Excepciones que puede lanzar |
@deprecated | Marca como obsoleta (en PHP 8.4 existe también el atributo #[\Deprecated]) |
@see, @link | Referencias a otro código o URL |
list<T>, array<K, V>, array{...} | Tipos de array detallados |
💡 Consejo: No repitas lo obvio. Si la firma es
function sumar(int $a, int $b): int, un PHPDoc que diga “@param int $a el primer número” no aporta nada. Documenta el porqué, las unidades, los rangos válidos y la forma de los arrays.
20. Errores comunes con funciones
| Error | Causa | Solución |
|---|---|---|
Undefined variable $x dentro de la función | Las funciones no ven variables exteriores | Pásala como parámetro (o use en closures) |
Cannot redeclare function | Fichero incluido dos veces o nombre repetido | Usa require_once y nombres únicos |
TypeError: Argument #1 must be of type int, string given | Tipo incorrecto con strict_types | Convierte el valor antes ((int)) o valida la entrada |
ArgumentCountError: Too few arguments | Falta un argumento obligatorio | Pasa todos los obligatorios o añade valor por defecto |
| La closure no ve el valor actualizado | use ($x) captura por valor | Usa use (&$x) si de verdad necesitas el cambio |
fn() => $total++ no modifica $total | Las arrow functions capturan por valor | Usa una closure con use (&$total) |
Error de sintaxis en un pipe con fn | Arrow function sin paréntesis | Escribe \|> (fn($x) => ...) |
Allowed memory size exhausted en recursión | Falta caso base o recursión demasiado profunda | Revisa el caso base o usa un bucle |
21. Ejercicios
Ejercicio 1: Biblioteca de conversiones
Crea un fichero lib/conversiones.php con funciones tipadas: celsiusAFahrenheit(float $c): float, fahrenheitACelsius(float $f): float, kmAMillas(float $km): float y formatearNumero(float $n, int $decimales = 2): string (con formato español). Inclúyelo desde index.php con require_once y muestra una tabla de conversiones del 0 al 100 °C de 10 en 10. Documenta cada función con PHPDoc.
Ejercicio 2: Estadísticas variádicas
Escribe estadisticas(int|float ...$numeros): array que devuelva un array con las claves min, max, media y mediana. Si no recibe números, debe devolver un array con todos los valores a null. Llámala con argumentos sueltos y también desplegando un array con .... Usa desestructuración para recoger el resultado.
Ejercicio 3: Generador de etiquetas HTML con argumentos con nombre
Crea enlace(string $url, string $texto, bool $nuevaPestana = false, string $clase = '', ?string $titulo = null): string que genere un <a> correcto (escapa los valores con htmlspecialchars). Haz 4 llamadas distintas usando argumentos con nombre para saltarte parámetros opcionales, por ejemplo enlace('https://php.net', 'PHP', clase: 'btn').
Ejercicio 4: Fábrica de validadores
Escribe funciones que devuelvan closures de validación: longitudMinima(int $min): Closure, contiene(string $texto): Closure y noVacio(): Closure. Cada closure recibe un string y devuelve bool. Después crea validar(string $valor, Closure ...$reglas): bool que devuelva true solo si se cumplen todas (pista: array_all() de PHP 8.4). Prueba a validar contraseñas.
Ejercicio 5: Contador con static y con closure
Implementa un contador de dos formas: (a) una función contador(): int con una variable static; (b) una función crearContador(int $inicio = 0): Closure que devuelva una closure con use (&$cuenta). Demuestra que con la opción (b) puedes tener varios contadores independientes y con la (a) no.
Ejercicio 6: Árbol de categorías recursivo
Dado un array plano de categorías [['id' => 1, 'nombre' => 'Informática', 'padre' => null], ['id' => 2, 'nombre' => 'Portátiles', 'padre' => 1], ...] con al menos 3 niveles, escribe una función recursiva construirArbol(array $categorias, ?int $padre = null): array que devuelva la estructura anidada, y otra pintarArbol(array $arbol, int $nivel = 0): void que la muestre con sangría.
Ejercicio 7: Procesador de texto con pipe
Usando exclusivamente el operador |>, escribe una función normalizarNombre(string $nombre): string que: elimine espacios sobrantes al principio y al final, reduzca los espacios internos múltiples a uno, ponga todo en minúsculas (multibyte) y la primera letra de cada palabra en mayúscula (mb_convert_case con MB_CASE_TITLE). Prueba con ' maría josé GARCÍA '. Después escribe la misma función con funciones anidadas y compara la legibilidad.
Ejercicio 8: Validación que no se puede ignorar
Crea una función comprobarStock(array $inventario, string $producto, int $cantidad): bool marcada con #[\NoDiscard] y un mensaje explicativo. Llámala una vez sin usar el resultado (observa el aviso), otra guardando el resultado en una variable y una tercera con (void). Explica en un comentario en qué casos tendría sentido usar (void).
Resumen
| Concepto | Sintaxis |
|---|---|
| Definir función | function nombre(int $a): int { return $a; } |
| Sin retorno | function f(): void { } |
| Tipos unión / nullable | int\|float $n, ?string $s |
| Valor por defecto | function f(string $m = '€') |
| Paso por referencia | function f(array &$datos) |
| Variádico | function f(int ...$nums) |
| Spread al llamar | f(...$array) |
| Argumento con nombre | f(ciudad: 'Sevilla') |
| Variable estática | static $n = 0; |
| Closure con captura | function ($x) use ($iva, &$total) { } |
| Arrow function | fn(int $x) => $x * $factor |
| First-class callable | strlen(...), $obj->metodo(...) |
| Orden superior | array_map, array_filter, array_reduce, usort |
| Recursividad | Caso base + llamada a sí misma |
| Pipe (8.5) | $x \|> trim(...) \|> (fn($s) => ...) |
| NoDiscard (8.5) | #[\NoDiscard("motivo")] y (void) f(); |
| Incluir ficheros | require_once __DIR__ . '/lib/f.php'; |
| PHPDoc | /** @param list<string> $x @return int */ |