Saltar al contenido
funciones.php · devschool

Funciones en PHP

Lección 11 de 26 · 13 min de lectura · Actualizado el

En esta lección
  1. Definir y llamar a una función
  2. Parámetros y return
  3. Tipos en parámetros y retorno
  4. Valores por defecto
  5. Argumentos con nombre
  6. Número variable de argumentos
  7. Paso por valor y por referencia
  8. Ámbito de las variables
  9. Funciones anónimas y closures
  10. Funciones flecha
  11. Recursividad
  12. Ejemplo completo: precio de una reserva
  13. Errores frecuentes
  14. Resumen

Una función es un bloque de código con nombre que puedes ejecutar tantas veces como quieras. En lugar de copiar el cálculo del precio con IVA en diez páginas, lo escribes una vez dentro de una función y lo llamas donde haga falta; si cambia el IVA, lo cambias en un único sitio. En esta lección aprenderás a definir funciones con tipos, a devolver resultados, a usar valores por defecto y argumentos con nombre, a entender el ámbito de las variables y a crear funciones anónimas y funciones flecha.

Definir y llamar a una función

Se define con la palabra function, un nombre, los paréntesis y el cuerpo entre llaves:

function saludar()
{
    echo "¡Bienvenida al rocódromo!\n";
}

saludar();
saludar();
// ¡Bienvenida al rocódromo!
// ¡Bienvenida al rocódromo!

Definir una función no la ejecuta: solo le pone nombre al código. Se ejecuta cada vez que la llamas escribiendo su nombre seguido de paréntesis.

Sobre los nombres:

  • En PHP moderno se escriben en camelCase y suelen empezar por un verbo: calcularTotal, enviarCorreo, esMayorDeEdad.
  • A diferencia de las variables, los nombres de funciones no distinguen mayúsculas: saludar() y SALUDAR() llaman a la misma. Aun así, escríbelos siempre igual.
  • No puedes definir dos funciones con el mismo nombre, ni usar el nombre de una función de PHP (count, strlen…).

Puedes llamar a una función antes de la línea donde está definida, siempre que esté en el mismo archivo y no dentro de un if. Aun así, lo más claro es definirlas arriba o, mejor, en un archivo aparte, como verás en include y require.

Parámetros y return

Una función se vuelve útil cuando recibe datos y devuelve un resultado. Los parámetros son las variables que declaras entre paréntesis; los argumentos, los valores concretos que pasas al llamarla. Con return la función devuelve un valor y termina:

function precioConIva(float $precio, float $iva): float
{
    return round($precio * (1 + $iva), 2);
}

$cuota = precioConIva(35, 0.21);
echo "Cuota mensual: $cuota €\n";
echo "Bono 10 sesiones: ", precioConIva(80, 0.21), " €\n";
// Cuota mensual: 42.35 €
// Bono 10 sesiones: 96.8 €

return o echo

Es la duda más común al empezar. Compara estas dos funciones:

function mostrarTotal(float $a, float $b): void
{
    echo $a + $b, "\n";
}

function sumar(float $a, float $b): float
{
    return $a + $b;
}

mostrarTotal(10, 5);
$total = sumar(10, 5) * 2;
echo $total, "\n";
// 15
// 30

mostrarTotal() imprime el resultado, pero no te lo da: no puedes guardarlo, multiplicarlo ni usarlo en una condición. sumar() lo devuelve y tú decides qué hacer con él. Regla práctica: las funciones calculan y devuelven; quien las llama decide si imprimir. Así sirven igual para una página HTML, para un JSON o para una prueba.

Detalles de return:

  • Termina la función en el acto: lo que haya debajo no se ejecuta.
  • Puedes tener varios return, por ejemplo uno en cada rama de un if.
  • Solo devuelve un valor. Si necesitas varios, devuelve un array y desestructúralo: [$min, $max] = rango($notas);.
  • Una función sin return devuelve null.

Tipos en parámetros y retorno

Desde PHP 7, y mucho más desde PHP 8, puedes indicar el tipo de cada parámetro y el del valor devuelto (detrás de ):). No es obligatorio, pero es muy recomendable: documenta la función, el editor te avisa de errores y PHP detecta llamadas incorrectas.

TipoSignificado
int, float, string, bool, arrayLos tipos básicos
?stringUna cadena o null (nullable)
int|floatUn entero o un decimal (tipo unión)
voidNo devuelve nada (solo en el retorno)
mixedCualquier cosa
callableAlgo que se puede llamar, como una función
function buscarSocio(array $socios, int $id): ?string
{
    return $socios[$id] ?? null;
}

$socios = [7 => "Irene", 12 => "Unai"];
var_dump(buscarSocio($socios, 12));
var_dump(buscarSocio($socios, 99));
// string(4) "Unai"
// NULL

El ? delante de string avisa a quien use la función de que puede recibir null y debe tenerlo en cuenta.

Como viste en Variables y tipos, sin declare(strict_types=1); PHP convierte los argumentos cuando puede (la cadena "5" pasa como int). Con él, un tipo incorrecto lanza un TypeError. Ponlo al principio de tus archivos: es la forma moderna de trabajar.

Valores por defecto

Un parámetro puede tener un valor por defecto, que se usa si no le pasas ese argumento. Los parámetros con valor por defecto van al final:

function formatearPrecio(float $cantidad, string $moneda = "€", int $decimales = 2): string
{
    return number_format($cantidad, $decimales, ",", ".") . " " . $moneda;
}

echo formatearPrecio(1250.5), "\n";
echo formatearPrecio(1250.5, "USD"), "\n";
echo formatearPrecio(1250.5, "€", 0), "\n";
// 1.250,50 €
// 1.250,50 USD
// 1.251 €

Argumentos con nombre

En la última llamada hemos tenido que repetir "€" solo para llegar al tercer parámetro. PHP 8 permite pasar argumentos por su nombre, en cualquier orden y saltándote los que tienen valor por defecto:

echo formatearPrecio(1250.5, decimales: 0), "\n";
echo formatearPrecio(moneda: "£", cantidad: 99), "\n";
// 1.251 €
// 99,00 £

Además de ahorrar argumentos, hacen las llamadas más legibles. Compara crearReserva("Ane", 4, true, false) con crearReserva("Ane", personas: 4, terraza: true, aviso: false). Funcionan también con las funciones de PHP: htmlspecialchars($texto, double_encode: false).

Número variable de argumentos

Si antepones ... al último parámetro, recoge todos los argumentos sobrantes en un array. Es un parámetro variádico:

function media(float ...$numeros): float
{
    if (count($numeros) === 0) {
        return 0;
    }
    return array_sum($numeros) / count($numeros);
}

echo media(7, 8.5, 6), "\n";
echo media(), "\n";

$notas = [9, 10, 8];
echo media(...$notas), "\n";
// 7.1666666666667
// 0
// 9

En la última llamada, ... hace lo contrario: desempaqueta el array para pasar cada elemento como un argumento separado.

Paso por valor y por referencia

Cuando pasas una variable a una función, PHP le pasa una copia de su valor. Lo que haga la función con su parámetro no afecta a la variable original. Esto vale también para los arrays:

function anadirExtra(array $carrito): array
{
    $carrito[] = "Magnesio";
    return $carrito;
}

$miCarrito = ["Cuerda"];
$nuevo = anadirExtra($miCarrito);

echo count($miCarrito), " y ", count($nuevo), "\n";
// 1 y 2

Si pones & delante del parámetro, la función recibe una referencia a la variable original y puede modificarla. Así funcionan sort() y compañía, como viste en Funciones de arrays:

function aplicarDescuento(float &$precio, float $porcentaje): void
{
    $precio = $precio * (1 - $porcentaje / 100);
}

$precio = 60;
aplicarDescuento($precio, 25);
echo $precio, "\n";
// 45

Úsalo poco. Una función que devuelve un resultado nuevo es más fácil de entender y de probar que una que cambia variables “a escondidas”.

Ámbito de las variables

El ámbito (scope) es la zona del código donde existe una variable. En PHP la regla es estricta: dentro de una función solo existen sus parámetros y las variables que cree ella misma. No ve las variables de fuera:

$iva = 0.21;

function totalConIva(float $base): float
{
    return $base * (1 + $iva);
}

echo totalConIva(100), "\n";
// Warning: Undefined variable $iva in ...
// 100

Esto sorprende si vienes de JavaScript, pero es una ventaja: una función no puede depender por accidente de una variable que alguien cambió en otra parte del programa. La solución correcta es pasarle lo que necesita como parámetro (o usar una constante, que sí es visible en todas partes).

En código antiguo verás la palabra global (global $iva;) o el array $GLOBALS para acceder a variables de fuera. Evítalos: hacen el código difícil de seguir.

Variables static

Las variables locales se crean al entrar en la función y desaparecen al salir. Una variable declarada con static conserva su valor entre llamadas:

function siguienteTicket(): int
{
    static $numero = 0;
    $numero++;
    return $numero;
}

echo siguienteTicket(), " ", siguienteTicket(), " ", siguienteTicket(), "\n";
// 1 2 3

El valor inicial (= 0) solo se asigna la primera vez. Recuerda que en una web cada petición empieza de cero: static no sirve para contar visitas entre usuarios, solo dentro de una misma ejecución.

Funciones anónimas y closures

Una función anónima es una función sin nombre que puedes guardar en una variable o pasar como argumento. Es justo lo que necesitan usort(), array_map() o array_filter():

$conEuro = function (float $n): string {
    return number_format($n, 2, ",", ".") . " €";
};

echo $conEuro(19.9), "\n";
echo implode(" / ", array_map($conEuro, [5, 12.5])), "\n";
// 19,90 €
// 5,00 € / 12,50 €

Fíjate en el ; tras la llave de cierre: es una asignación.

Las funciones anónimas tampoco ven las variables de fuera. Para usar una, tienes que capturarla con use. A una función que captura variables de su entorno se le llama closure:

$minimo = 18;

$adultos = array_filter(
    ["Ane" => 17, "Hugo" => 34, "Olga" => 18],
    function (int $edad) use ($minimo): bool {
        return $edad >= $minimo;
    }
);

echo implode(", ", array_keys($adultos)), "\n";
// Hugo, Olga

use captura una copia del valor en el momento de crear la función. Si después cambias $minimo, la función no se entera.

Funciones flecha

Las funciones flecha (PHP 7.4) son la forma corta de una función anónima de una sola expresión. Se escriben con fn, no llevan llaves ni return y capturan automáticamente las variables de fuera:

$minimo = 18;
$edades = ["Ane" => 17, "Hugo" => 34, "Olga" => 18];

$adultos = array_filter($edades, fn(int $edad): bool => $edad >= $minimo);
echo implode(", ", array_keys($adultos)), "\n";

$duplicar = fn($n) => $n * 2;
echo $duplicar(21), "\n";
// Hugo, Olga
// 42
function () use (...) { }fn() => ...
CuerpoVarias instruccionesUna sola expresión
returnObligatorioImplícito
Variables de fueraSolo las indicadas en useTodas, automáticamente (por valor)
Uso típicoLógica de varias líneasCallbacks cortos en array_map, usort…

Una función que recibe otra función como argumento, como array_map, se llama función de orden superior, y la función que le pasas, callback. Puedes escribir las tuyas con el tipo callable:

function aplicarATodos(array $datos, callable $transformar): array
{
    $resultado = [];
    foreach ($datos as $clave => $valor) {
        $resultado[$clave] = $transformar($valor);
    }
    return $resultado;
}

print_r(aplicarATodos(["a" => "hola", "b" => "adiós"], 'strtoupper'));
// Array
// (
//     [a] => HOLA
//     [b] => ADIóS
// )

¿Y esa ó minúscula? strtoupper() solo entiende letras sin tilde. Para textos en español usa mb_strtoupper(), que verás en Cadenas.

Recursividad

Una función recursiva es la que se llama a sí misma. Necesita un caso base que detenga las llamadas; si no, se repetiría hasta agotar la memoria:

function cuentaAtras(int $n): void
{
    if ($n === 0) {
        echo "¡Ya!\n";
        return;
    }
    echo $n, "... ";
    cuentaAtras($n - 1);
}

cuentaAtras(3);
// 3... 2... 1... ¡Ya!

Es útil con estructuras anidadas, como un menú con submenús o un árbol de carpetas. Para contar o recorrer listas, un bucle suele ser más claro.

Ejemplo completo: precio de una reserva

declare(strict_types=1);

const IVA = 0.21;

function precioReserva(
    int $personas,
    float $precioPorPersona,
    bool $esSocio = false,
    ?string $codigo = null,
): array {
    $base = $personas * $precioPorPersona;

    $descuento = match (true) {
        $esSocio => 0.15,
        $codigo === "VERANO" => 0.10,
        default => 0,
    };

    $neto = $base * (1 - $descuento);

    return [
        "base" => round($base, 2),
        "descuento" => round($base - $neto, 2),
        "total" => round($neto * (1 + IVA), 2),
    ];
}

$reserva = precioReserva(4, 12.0, codigo: "VERANO");
echo "Base: {$reserva['base']} €, descuento: {$reserva['descuento']} €, total: {$reserva['total']} €\n";

["total" => $total] = precioReserva(2, 12.0, esSocio: true);
echo "Socio, 2 personas: $total €\n";
// Base: 48 €, descuento: 4.8 €, total: 52.27 €
// Socio, 2 personas: 24.68 €

La coma después del último parámetro está permitida desde PHP 8 y facilita añadir parámetros nuevos. El match lo viste en Condicionales.

Errores frecuentes

  • Imprimir en lugar de devolver: si la función hace echo, no podrás usar su resultado. Devuelve con return y deja que quien llama decida.
  • Usar una variable de fuera dentro de la función: da Undefined variable. Pásala como parámetro o captúrala con use.
  • Poner un parámetro opcional antes de uno obligatorio: function f($a = 1, $b) está obsoleto en PHP 8. Los opcionales van al final.
  • Olvidar el ; al guardar una función anónima en una variable: $f = function () { ... };.
  • Esperar que la función cambie el array que le pasas: recibe una copia. Devuelve el array nuevo (o usa & si de verdad lo necesitas).
  • Funciones demasiado largas: si no cabe en la pantalla o hace tres cosas distintas, divídela en varias.

Resumen

ConceptoEjemplo
Definirfunction sumar(int $a, int $b): int { return $a + $b; }
Llamar$total = sumar(2, 3);
Sin valor de retornofunction mostrar(): void { ... }
Nullable y unión?string, int|float
Valor por defectofunction f(string $moneda = "€")
Argumentos con nombreformatearPrecio(99, decimales: 0)
Variádicosfunction media(float ...$n) y media(...$array)
Por referenciafunction f(array &$a)
ÁmbitoDentro solo existen los parámetros y las variables locales
staticConserva el valor entre llamadas
Anónima / closurefunction ($x) use ($y) { return ...; }
Flechafn($x) => $x * 2

En la siguiente lección aprenderás a repartir tu código en varios archivos: include y require.

Pon a prueba lo que has aprendido

[PHP] ¿Qué muestra este código?
function siguiente() {
    static $n = 0;
    return ++$n;
}
siguiente();
siguiente();
echo siguiente();

[PHP] ¿Qué muestra este código?
$k = 10;
$f = fn($x) => $x + $k;
$k = 100;
echo $f(1);

[PHP] ¿Qué muestra este código?
function a(array $x) { $x[] = 1; }
function b(array &$x) { $x[] = 1; }

$lista = [];
a($lista);
b($lista);
echo count($lista);

[PHP] ¿Qué devuelve h(1, c: 9)?
function h($a, $b = 2, $c = 3) {
    return "$a$b$c";
}

¿Te ha quedado claro? Márcala y verás tu progreso en el explorador.