PHPlay

Curso de PHP gratis · Capítulo 8 de 13 · con Iria Forja

Funciones en PHP: return, parámetros, tipos y recursividad

Las funciones te dejan escribir un bloque de código una vez y usarlo mil. Aprenderás a declararlas, tiparlas y a entender la pila de llamadas y la recursividad.

Forjar una función

Hasta ahora has llamado funciones de PHP (strlen, count...) y de BIT-8. Ahora vas a declarar las tuyas:

function saludar($nombre) {
    return "¡Hola, $nombre!";
}
  • function + nombre + paréntesis con los parámetros + bloque.
  • return devuelve un valor y termina la función en ese momento.
  • Se usa igual que cualquier otra: echo saludar("Vera");

Beneficios: escribes el código una vez (principio DRY: Don't Repeat Yourself), le das un nombre que explica qué hace, y si hay un bug lo arreglas en un solo sitio.

Truco

Las funciones se pueden llamar incluso antes de declararlas en el archivo: PHP las registra todas antes de empezar a ejecutar.

<?php
echo saludar("Vera"), "\n";

function saludar($nombre) {
    return "¡Hola, $nombre!";
}

function area($ancho, $alto) {
    return $ancho * $alto;
}
echo saludar("Forja"), "\n";
echo "Área: ", area(3, 4), "\n";
Salida
¡Hola, Vera!
¡Hola, Forja!
Área: 12

return

Algunas cosas importantes sobre return:

  • Puedes tener varios return (por ejemplo, dentro de un if). El primero que se ejecuta termina la función.
  • Si una función termina sin return, devuelve null.
  • Devolver no es imprimir. Una función que hace echo escribe en pantalla, pero no te da ningún valor para seguir trabajando. Casi siempre es mejor que la función devuelva y que decida quien la llama si lo imprime.
Cuidado

Error típico: olvidar el return y preguntarse por qué la variable está vacía.

<?php
function signo($n) {
    if ($n > 0) {
        return "positivo";
    }
    if ($n < 0) {
        return "negativo";
    }
    return "cero";
}
echo signo(-4), " ", signo(0), "\n";

function mal($a, $b) {
    $suma = $a + $b;
}
var_dump(mal(2, 3));
Salida
negativo cero
NULL

Valores por defecto y argumentos con nombre

Un parámetro puede tener un valor por defecto: si no lo pasas, se usa ese.

function precio($base, $descuento = 0) { ... }
precio(100);       // descuento = 0
precio(100, 20);   // descuento = 20

Los parámetros con valor por defecto van al final.

Argumentos con nombre (PHP 8): puedes indicar a qué parámetro va cada valor, en cualquier orden, y saltarte los que tienen valor por defecto:

precio(descuento: 50, base: 10);

Si llamas a una función con menos argumentos de los obligatorios, PHP lanza un ArgumentCountError.

<?php
function etiqueta($producto, $cantidad = 1, $unidad = "sacos") {
    return "$cantidad $unidad de $producto";
}
echo etiqueta("heno"), "\n";
echo etiqueta("heno", 5), "\n";
echo etiqueta("leche", 2, "litros"), "\n";
echo etiqueta(unidad: "cajas", producto: "fresas"), "\n";
Salida
1 sacos de heno
5 sacos de heno
2 litros de leche
1 cajas de fresas

Tipos en parámetros y retorno

Puedes declarar el tipo de cada parámetro y del valor devuelto:

function area(int $lado): int {
    return $lado * $lado;
}

Tipos habituales: int, float, string, bool, array, ?int (int o null), int|string (uno u otro), void (no devuelve nada), mixed (cualquiera).

¿Qué pasa si pasas "5" a un parámetro int? Depende del modo:

  • Modo coercitivo (por defecto): PHP intenta convertirlo. "5" → 5. Pero "hola" no se puede: TypeError.
  • Modo estricto: poniendo declare(strict_types=1); al principio del archivo, NO convierte nada: "5" a un int es un TypeError. (La única excepción: un int se acepta donde se pide un float.)
Pergamino del porque

Los tipos son un contrato: documentan qué espera la función y detectan errores cerca de donde se producen, en vez de muchas líneas después. Muchos equipos usan strict_types=1 en todos sus archivos.

<?php
function area(int $lado): int {
    return $lado * $lado;
}
echo area(3), "\n";
echo area("4"), "\n";
echo area(2.0), "\n";
echo area("cuatro"), "\n";
Salida
9
16
4

La pila de llamadas

¿Qué pasa en la memoria cuando llamas a una función?

  1. PHP crea un marco (frame) nuevo para esa llamada, con su propia tabla de símbolos, vacía.
  2. Los parámetros son variables locales de ese marco, con una copia de los valores que pasaste.
  3. La función se ejecuta usando su tabla.
  4. Al hacer return, el marco se destruye: sus variables locales desaparecen.

Si una función llama a otra, los marcos se apilan unos sobre otros: es la pila de llamadas (call stack).

Pergamino del porque

Por eso una función no puede ver las variables de fuera (ni las de quien la llamó), y por eso modificar un parámetro no cambia la variable original: la función trabaja con sus propias cajas. Pulsa paso a paso en la Lupa y fíjate en el ámbito que se muestra arriba: dentro de duplica().

<?php
function duplica($n) {
    $n = $n * 2;
    return $n;
}
$x = 5;
$y = duplica($x);
echo "x sigue valiendo $x, y vale $y\n";
Salida
x sigue valiendo 5, y vale 10
Pregunta

Después de llamar a duplica($x), ¿qué pasa con la variable $n de la función?

  1. Sigue existiendo en el programa principal
  2. Desaparece al terminar la función
  3. Se convierte en $x
Ver respuesta

B. Desaparece al terminar la función

$n vivía en el marco de la llamada. Al terminar la función el marco se destruye y sus variables con él.

Recursividad

Una función puede llamarse a sí misma. Se llama recursividad, y es muy útil cuando un problema se puede dividir en versiones más pequeñas de sí mismo:

function factorial(int $n): int {
    if ($n <= 1) {
        return 1;                    // caso base
    }
    return $n * factorial($n - 1);   // caso recursivo
}

Toda función recursiva necesita un caso base que no se llame a sí mismo. Si no, cada llamada apila un marco nuevo... hasta que la pila se desborda (stack overflow).

Usa el paso a paso para ver cómo se apilan las llamadas: factorial(4) espera a factorial(3), que espera a factorial(2)...

<?php
function factorial(int $n): int {
    if ($n <= 1) {
        return 1;
    }
    return $n * factorial($n - 1);
}
echo factorial(4), "\n";
echo factorial(10), "\n";
Salida
24
3628800

Funciones para BIT-8

Con funciones, tus programas de la granja se vuelven mucho más claros. Por ejemplo, una función ir_a($x, $y) que lleve a BIT-8 a cualquier casilla, o cosechar_fila().

Una vez forjadas, tu programa principal se lee casi como un cuento:

ir_a(0, 0);
cosechar_campo();
plantar_zanahorias();
Truco

Buenas prácticas: nombres con verbo (calcular_total, ir_a), funciones cortas que hacen una sola cosa, y preferir devolver valores a imprimir desde dentro.

<?php
function cosechar_fila(int $n): void {
    for ($i = 0; $i < $n; $i++) {
        if (puede_cosechar()) {
            cosechar();
        }
        mover(ESTE);
    }
}

function cosechar_campo(): void {
    $n = tam();
    for ($f = 0; $f < $n; $f++) {
        cosechar_fila($n);
        mover(NORTE);
    }
}

cosechar_campo();

Ejercicios de Funciones en PHP resueltos

Intenta resolverlos antes de mirar la solución. En el juego puedes ejecutarlos y comprobarlos automáticamente.

Encargo 1. Tu primera función

Tuerca necesita calcular cuántas casillas tiene un campo cuadrado de lado N.

Declara una función area_campo(int $lado): int que devuelva el número de casillas (lado × lado). No imprimas nada dentro: la probaremos nosotros.

Pistas
  • function area_campo(int $lado): int { ... }
  • Dentro: return $lado * $lado;
Ver solución
<?php
function area_campo(int $lado): int {
    return $lado * $lado;
}

Encargo 2. Precio con descuento

Leo quiere una función para calcular precios rebajados.

Declara precio_final(float $precio, int $descuento = 0): float que devuelva el precio aplicando el porcentaje de descuento. Por ejemplo, precio_final(80, 25) debe devolver 60.

Pistas
  • El precio con descuento es $precio * (100 - $descuento) / 100.
  • No olvides el return.
Ver solución
<?php
function precio_final(float $precio, int $descuento = 0): float {
    return $precio * (100 - $descuento) / 100;
}

Misión del dron 3. ir_a(x, y)

Forja quiere que BIT-8 recoja tres encargos en casillas concretas. Escribir cada ruta a mano es un rollo...

Declara ir_a($x, $y), que lleve a BIT-8 a la casilla (x, y) desde donde esté (pista: pos_x() y pos_y()). Después úsala para cosechar, en este orden, las casillas (2, 1), (0, 2) y (1, 0).

Esta misión mueve al dron BIT-8 por un campo de cultivo: juégala en PHPlay para ver tu código animado.

Pistas
  • while (pos_x() != $x) { mover(ESTE); } (el campo da la vuelta, así que siempre llega).
  • Haz lo mismo con la y y mover(NORTE).
  • Para el extra: calcula los pasos hacia el ESTE con ($x - pos_x() + tam()) % tam(); si son más de la mitad del campo, es más corto ir al OESTE.
Ver solución
<?php
// Version con el camino mas corto (aprovecha que el campo es un donut)
function ir_a($x, $y) {
    $n = tam();
    $dx = ($x - pos_x() + $n) % $n;   // pasos hacia el ESTE
    $dy = ($y - pos_y() + $n) % $n;   // pasos hacia el NORTE
    for ($i = 0; $i < min($dx, $n - $dx); $i++) {
        mover($dx <= $n - $dx ? ESTE : OESTE);
    }
    for ($i = 0; $i < min($dy, $n - $dy); $i++) {
        mover($dy <= $n - $dy ? NORTE : SUR);
    }
}

ir_a(2, 1);
cosechar();
ir_a(0, 2);
cosechar();
ir_a(1, 0);
cosechar();

Predicción 4. La isla de la función

Forja escribe en la pizarra. ¿Qué imprime?

Elige la salida.

<?php
function mejora($nivel) {
    $nivel = $nivel + 10;
    $extra = 1;
    return $nivel * 2;
}
$nivel = 5;
$resultado = mejora($nivel);
echo $nivel, " ", $resultado, " ", $extra ?? "sin extra";
  1. 5 30 sin extra
  2. 15 30 1
  3. 5 30 1
  4. 15 20 sin extra
Ver solución
5 30 sin extra

Eso es: la función trabaja con su propia copia de $nivel y $extra muere con su marco.

Caza de bugs 5. La función muda

Esta función debería devolver la suma de un array, pero el resultado sale vacío.

Arregla la función para que el programa imprima exactamente La suma es 7.

Código de partida:

<?php
function sumar_todo(array $numeros): int {
    $total = 0;
    foreach ($numeros as $n) {
        $total += $n;
    }
}
echo "La suma es ", sumar_todo([3, 4]);
Pistas
  • Lee el error: la función promete devolver un int... ¿lo hace?
  • Añade return $total; al final de la función.
Ver solución
<?php
function sumar_todo(array $numeros): int {
    $total = 0;
    foreach ($numeros as $n) {
        $total += $n;
    }
    return $total;
}
echo "La suma es ", sumar_todo([3, 4]);

Encargo 6. Potencia recursiva

Forja te reta: calcular potencias sin usar ** ni pow().

Declara potencia(int $base, int $exp): int de forma recursiva: potencia($b, 0) es 1 y potencia($b, $e) es $b * potencia($b, $e - 1).

Pistas
  • Caso base: if ($exp === 0) { return 1; }
  • Caso recursivo: return $base * potencia($base, $exp - 1);
Ver solución
<?php
function potencia(int $base, int $exp): int {
    if ($exp === 0) {
        return 1;
    }
    return $base * potencia($base, $exp - 1);
}

Chuleta: lo esencial

  • function nombre(tipo $param = defecto): tipoRetorno { return valor; }
  • return devuelve y termina. Sin return → null. Devolver ≠ imprimir.
  • Parámetros con valor por defecto al final. Argumentos con nombre: f(b: 2, a: 1).
  • Tipos: int float string bool array ?int int|string void mixed. Coercitivo por defecto; declare(strict_types=1); no convierte.
  • Cada llamada crea un marco con su propia tabla de símbolos; los parámetros son copias; al volver, se destruye. Pila de llamadas.
  • Recursividad: caso base + caso recursivo. Sin caso base → desbordamiento de pila.
  • ir_a($x, $y): while (pos_x() != $x) mover(ESTE); ...