7. Funciones

Cuando utilizamos en multiples ocasiones un mismo fragmento de código debemos usar funciones (functions). Una herramienta para encapsular y ejecutar un mismo código. Entre sus ventajas veremos que nos ayuda a que los ficheros tengan un menor tamaño y sea más fácil de mantener.

// Declarar
function nombre_de_funcion(tipo_de_parametro $parametros): tipo_return
{
    ...
    return ...;
}

// Llamar
nombre_de_funcion($parametros);

En el ejemplo inferior tenemos una función clásica, con sintáxis moderna, donde es declarada y ejecutada seguido de un echo para ver el resultado. Lo único que hace es devolver un texto cuando es llamado.

/**
 * Función con educación
 * @return {string}
 */
function saludar_ahora(): string
{
    return 'Hola, soy una función';
}
echo saludar_ahora();
// Hola, soy una función

La receta par hacer una función rica y con fundamento:

  • Las 4 primeras líneas es el formato de comentarios. Una función, por mucha prisa que tengas, debe estar comentada con el formato del ejemplo.
  • La palabra function esta reservada. A continuación el nombre, que debe estar en minúsculas con guiones bajos en lugar de espacios. Después unos paréntesis con los argumentos. Si no tiene, se dejan vacíos pero siempre presentes. Luego dos puntos. Por último el tipo de valor resultante. Más abajo te he dejado una tabla con tipos admitidos.
  • Llaves para envolver tu código {}.
  • Y dentro la palabra reservada return seguido del valor a devolver.

Parámetros

Nuestras funciones serán más interesantes si les damos algunos parámetros. Al darle variables podemos usar siempre el mismo código pero con algunas variaciones.

/**
 * Corta un texto a 10 letras y añade puntos suspensivos al final
 * @param {string} $text - Texto a tratar
 * @return {string}
 */
function resumen(string $text): string
{
    return substr($text, 0, 20) . '...';
}

echo resumen('Cuanto te vi me enamoré y tu sonreíste porque lo sabías');
echo resumen('La vida es una historia contada por un idiota, una historia llena de estruendo y furia, que nada significa');
// Cuanto te vi me enam...
// La vida es una histo...

Si no se cumple los tipos en PHP 5 produce un error que para la ejecución. En cambio, en PHP 7, se lanza una excepción TypeError pero continua.

Nuestros parámetros de entrada pueden tener un valor por defecto.

/**
 * Saluda a una persona
 * @param {string} - Nombre
 * @return {string}
 */
function saludar(string $nombre = 'Anónimo'): string
{
    return 'Hola, persona llamada ' . $nombre .'. Por lo que veo tu nombre mide ' . strlen($nombre) . ' carácteres.';
}
echo saludar();
// Hola, persona llamada Anónimo. Por lo que veo tu nombre mide 8 carácteres.
echo saludar('Picasso');
// Hola, persona llamada Picasso. Por lo que veo tu nombre mide 7 carácteres.

Y, por supuesto, podemos añadir varios parámetros.

/**
 * Saluda a una persona
 * @param {string} - Nombre
 * @param {string} - Profesión
 * @return {string}
 */
function saludar(string $nombre = 'Anónimo', string $profesion = 'ninguna'): string
{
    return 'Hola, persona llamada ' . $nombre .'. Por lo que veo tu nombre mide ' . strlen($nombre) . ' carácteres. De profesión ' . $profesion . '.';
}
echo saludar();
// Hola, persona llamada Anónimo. Por lo que veo tu nombre mide 8 carácteres. De profesión ninguna.
echo saludar('Espartaco');
// Hola, persona llamada Espartaco. Por lo que veo tu nombre mide 9 carácteres. De profesión ninguna.
echo saludar('Picasso', 'pintor');
// Hola, persona llamada Picasso. Por lo que veo tu nombre mide 7 carácteres. De profesión pintor.

Tipos admitidos

Tipo Descripción Versión mínima
string El parámetro debe ser un string. PHP 7.0.0
int El parámetro debe ser un valor de tipo integer. PHP 7.0.0
float El parámetro debe ser un número de tipo float. PHP 7.0.0
bool El parámetro debe ser un valor de tipo boolean. PHP 7.0.0
array El parámetro debe ser un array. PHP 5.1.0
callable El parámetro debe ser un callable válido. PHP 5.4.0
self El parámetro debe ser una instanceof de la misma clase donde está definido el método. Esto solamente se puede utilizar en clases y métodos de instancia. PHP 5.0.0
nombre de clase/interfaz El parámetro debe ser una instanceof del nombre de la clase o interfaz dada. PHP 5.0.0

Más información

De forma automática PHP arreglará las incompatibilidades de tipos.

function incrementar(int $num): int
{
    return $num + 1;
}

echo incrementar(4.5);
// 5

Pero si quieres ser estricto deberás desactivar esta ayuda. En otras palabras, que si encuentra algún problema de tipos muestre un error y no aplique una mágica solución.

declare(strict_types=1);

function incrementar(int $num): int
{
    return $num + 1;
}

echo incrementar(4.5);
// PHP Fatal error:  Uncaught TypeError: Argument 1 passed to incrementar() must be of the type int, float given

Mejores prácticas: Type Hints en todo tu código

Ahora que conoces los tipos disponibles y cómo usarlos, hablemos de cuándo usarlos. La respuesta corta: siempre. Añadir type hints a tus funciones es una de las mejores prácticas en PHP moderno.

¿Por qué usar type hints?

1. Detección temprana de errores: Los errores de tipo se detectan inmediatamente en lugar de producir bugs misteriosos más adelante.

// Sin type hints - el error puede aparecer mucho después
function calcularDescuento($precio, $porcentaje) {
    return $precio - ($precio * $porcentaje / 100);
}

// Si alguien pasa un string, PHP intentará convertirlo
echo calcularDescuento('mil', '20'); // Resultado: 0 (incorrecto pero no da error)

// Con type hints - error inmediato y claro
function calcularDescuentoSeguro(float $precio, float $porcentaje): float {
    return $precio - ($precio * $porcentaje / 100);
}

echo calcularDescuentoSeguro('mil', '20'); // TypeError: debe ser float

2. Mejor autocompletado en tu editor: Tu IDE puede sugerirte métodos y propiedades correctos porque sabe el tipo exacto.

3. Documentación automática: El código se explica por sí mismo sin necesidad de comentarios adicionales.

// ¿Qué recibe esta función? ¿Qué devuelve? No está claro
function procesar($datos) {
    // ...
}

// Aquí queda cristalino
function procesarUsuario(array $datos, bool $enviarEmail = false): ?int {
    // Recibe un array y un booleano opcional
    // Devuelve un int (probablemente un ID) o null si falla
}

4. Prevención de bugs: Si cambias el tipo que devuelve una función, PHP te avisará de todos los lugares donde se usa incorrectamente.

Ejemplo: Función sin type hints vs con type hints

Veamos una función típica de una aplicación real:

// ❌ Código sin type hints (antiguo, poco claro, propenso a errores)
function crearPost($titulo, $contenido, $autor, $publicado = true) {
    // ¿$titulo es string? ¿$autor es un objeto? ¿un ID?
    // ¿$publicado es bool o int (0/1)?
    // No lo sabemos sin leer todo el código

    $post = [
        'titulo' => $titulo,
        'contenido' => $contenido,
        'autor_id' => $autor,
        'publicado' => $publicado,
        'fecha' => date('Y-m-d H:i:s')
    ];

    // Insertar en base de datos...
    return true;
}

Ahora con type hints:

// ✅ Código con type hints (moderno, claro, seguro)
function crearPost(
    string $titulo,
    string $contenido,
    int $autorId,
    bool $publicado = true
): array {
    // Ahora está cristalino:
    // - titulo y contenido son strings
    // - autorId es un entero (ID del autor)
    // - publicado es un booleano
    // - devuelve un array

    $post = [
        'titulo' => $titulo,
        'contenido' => $contenido,
        'autor_id' => $autorId,
        'publicado' => $publicado,
        'fecha' => date('Y-m-d H:i:s')
    ];

    // Insertar en base de datos...
    return $post;
}

// Uso claro y autocompletado perfecto
$nuevoPost = crearPost(
    titulo: 'Mi primer post',
    contenido: 'Este es el contenido...',
    autorId: 42,
    publicado: true
);

Cuándo NO usar type hints (casos excepcionales)

Hay situaciones donde no puedes o no debes usar type hints:

1. Funciones que aceptan múltiples tipos intencionalmente:

// Esta función acepta int, float o string
function formatearNumero(int|float|string $numero): string {
    return number_format((float)$numero, 2);
}

echo formatearNumero(1234.5);  // "1,234.50"
echo formatearNumero('1234.5'); // "1,234.50"

2. Arrays con estructura compleja: PHP no permite especificar la estructura interna de arrays.

// No puedes especificar que es un array de objetos Usuario
function procesarUsuarios(array $usuarios): void {
    // Tendrías que validar manualmente o usar un comentario
    foreach ($usuarios as $usuario) {
        // ...
    }
}

Tip profesional: En PHP moderno, usa declare(strict_types=1); al inicio de cada archivo. Esto obliga a que los tipos sean exactos y previene conversiones automáticas silenciosas que pueden ocultar bugs.

<?php
declare(strict_types=1);

// Ahora todos los type hints en este archivo son estrictos
function sumar(int $a, int $b): int {
    return $a + $b;
}

sumar(5, 3);    // ✅ Correcto
sumar(5.5, 3);  // ❌ TypeError

Resumen

Regla simple: Añade type hints a todos los parámetros y returns de tus funciones. Te ahorrará incontables horas de debugging y hará tu código mucho más profesional y mantenible.

Return con tipos alternativos

A partir de la versión 7.1 de PHP disponemos de la posibilidad de indicar si un return devuelve un tipo concreto o un null. Para ello solo habrá que añadir un interrogante en su inicio.

function nombre(): ?string
{

}

En el siguiente ejemplo podremos devolver un string o un null. Dependiendo de si el ganador esta entre los 3 primeros o no.

/**
 * Método que indica el tipo de metal que debe tener una medalla a partir del resultado
 * @param {int} $posicion - Posición
 * @return {string|null} - Tipo de metal
 */
function tipoDeMedalla(int $posicion): ?string
{
    switch ($posicion) {
        case 1:
            return 'Oro';
        case 2:
            return 'Plata';
        case 3:
            return 'Bronce';
        default:
            return null;
    }
}

echo tipoDeMedalla(2);
// Plata

echo tipoDeMedalla(34);
// null

Tal vez fue añadido esta característica por lo práctico que resulta al realizar testing.

Y de la versión PHP 8 se enriquece más las posibilidades ya que es posible indicar 2 tipos diferentes.

function nombre(): int|string
{

}

Nos puede ayudar en casos como, por ejemplo, dar un resultado alternativo o de seguridad.

/**
 * Método que duplica un número en positivo
 * @param {int} $numero - Número a duplicar
 * @return {float|string} - Resultado o mensaje de ayuda
 */
function duplicarPositivo(float $numero): float|string
{
    if ($numero > 0) {
        return $numero * 2;
    } else {
        return 'No puedes usar número en negativo o cero';
    }
}

echo duplicarPositivo(12.1);
// 24.2

echo duplicarPositivo(-45);
// 'No puedes usar número en negativo o cero'

Named Arguments (PHP 8.0+)

Los argumentos nombrados permiten pasar valores a una función especificando el nombre del parámetro, en lugar de depender del orden. Esto hace el código más legible y flexible.

// Función tradicional
function crearUsuario(string $nombre, int $edad, string $email = '', bool $activo = true) {
    return "Usuario: $nombre, Edad: $edad, Email: $email, Activo: " . ($activo ? 'Sí' : 'No');
}

// Forma tradicional (orden estricto)
echo crearUsuario('Ana', 25, 'ana@example.com', true);

// Con named arguments (cualquier orden)
echo crearUsuario(
    edad: 25,
    nombre: 'Ana',
    email: 'ana@example.com',
    activo: true
);

// Puedes omitir argumentos opcionales fácilmente
echo crearUsuario(
    nombre: 'Bob',
    edad: 30
    // email y activo usan valores por defecto
);

Ventajas principales:

  1. Legibilidad: Se ve claramente qué valor corresponde a cada parámetro
  2. Flexibilidad: No importa el orden de los argumentos
  3. Argumentos opcionales: Puedes saltar parámetros intermedios

Ejemplo práctico con setcookie():

// Antes (difícil de leer)
setcookie('token', $value, time() + 3600, '/', '', true, true);

// Con named arguments (mucho más claro)
setcookie(
    name: 'token',
    value: $value,
    expires_or_options: time() + 3600,
    path: '/',
    secure: true,
    httponly: true
);

Puedes mezclar argumentos posicionales y nombrados:

function mostrarInfo(string $nombre, int $edad, string $ciudad = 'Madrid') {
    return "$nombre tiene $edad años y vive en $ciudad";
}

// Mezclar posicionales y nombrados (posicionales primero)
echo mostrarInfo('Carlos', 28, ciudad: 'Barcelona');

Anónimas

Las funciones anónimas son cerradas y pueden ser declaradas sin ningún nombre. Son obligatorias cuando tengamos que pasar una función como un parámetro de otra.

function () {
    return 'Soy anónima';
}

En el siguiente ejemplo incrementamos en 1 cada número del array.

$numeros = [10, 20, 30, 40];
$numerosIncrementados = array_map(function ($numero) {
    return $numero + 1;
}, $numeros);

var_dump($numerosIncrementados);

/*
array(4) {
  [0]=>
  int(11)
  [1]=>
  int(21)
  [2]=>
  int(31)
  [3]=>
  int(41)
}
*/

Usar variables externas

Si vas a usar variables que están presentes en tu código, puedes enriquecer el contenido de la función usando use.

$tienda = 'pescadería';

function () use ($tienda) {
    return "Estoy en la $tienda";
}

Arrow Functions (PHP 7.4+)

Las arrow functions (funciones flecha) son una sintaxis más concisa para funciones anónimas, introducida en PHP 7.4. Su mayor ventaja es que capturan automáticamente las variables del ámbito padre sin necesidad de usar use.

// Función anónima tradicional
$numeros = [1, 2, 3, 4];
$factor = 10;
$multiplicados = array_map(function ($n) use ($factor) {
    return $n * $factor;
}, $numeros);

// Arrow function (más concisa)
$multiplicados = array_map(fn($n) => $n * $factor, $numeros);
// [10, 20, 30, 40]

Características importantes:

  • Se declaran con fn en lugar de function
  • Usan => para separar parámetros del cuerpo
  • Solo pueden tener una expresión (que se retorna automáticamente)
  • Capturan automáticamente variables del ámbito padre
  • No necesitan return explícito

Ejemplos prácticos:

$precios = [100, 200, 300];
$iva = 0.21;

// Calcular precio con IVA
$preciosConIVA = array_map(fn($precio) => $precio * (1 + $iva), $precios);
// [121, 242, 363]

// Filtrar números pares
$numeros = [1, 2, 3, 4, 5, 6];
$pares = array_filter($numeros, fn($n) => $n % 2 === 0);
// [2, 4, 6]

// Convertir a mayúsculas
$nombres = ['ana', 'bob', 'carlos'];
$mayusculas = array_map(fn($nombre) => strtoupper($nombre), $nombres);
// ['ANA', 'BOB', 'CARLOS']

Paradigma Funcional

Las funciones esenciales para iterar y gestionar un array son: array_walk, array_filter, array_map y array_reduce.

array_walk (Iterar)

Recorre un array, similar a un foreach.

array_walk({array}, {función});

En este ejemplo vamos a imprimir todas las ciudades.

<?php

// Diccionario
$apartamentos = [
    [
        'precio/noche' => 40,
        'ciudad' => 'Valencia',
        'wifi' => True,
        'pagina web' => 'https://hotel.com'
    ],
    [
        'precio/noche' => 87,
        'ciudad' => 'Calpe',
        'wifi' => True,
        'pagina web' => 'https://calpe.com'
    ],
    [
        'precio/noche' => 67,
        'ciudad' => 'Valencia',
        'wifi' => False,
        'pagina web' => 'https://denia.com'
    ],
    [
        'precio/noche' => 105,
        'ciudad' => 'Benidorm',
        'wifi' => False,
        'pagina web' => 'https://benidorm.com'
    ]
];

array_walk($apartamentos, function ($apartamento, $posicion) {
    echo $apartamento['ciudad'] . PHP_EOL;
});

/*
Valencia
Calpe
Valencia
Benidorm
*/

array_filter (filtrar)

Obtenemos un array a otro más pequeño.

array_filter({array}, {función});

En este ejemplo filtraremos $apartamentos para quedarnos con los que están en Valencia.

<?php

// Diccionario
$apartamentos = [
    [
        'precio/noche' => 40,
        'ciudad' => 'Valencia',
        'wifi' => True,
        'pagina web' => 'https://hotel.com'
    ],
    [
        'precio/noche' => 87,
        'ciudad' => 'Calpe',
        'wifi' => True,
        'pagina web' => 'https://calpe.com'
    ],
    [
        'precio/noche' => 67,
        'ciudad' => 'Valencia',
        'wifi' => False,
        'pagina web' => 'https://denia.com'
    ],
    [
        'precio/noche' => 105,
        'ciudad' => 'Benidorm',
        'wifi' => False,
        'pagina web' => 'https://benidorm.com'
    ]
];


$todosLosApartamentosValencia = array_filter($apartamentos, function ($apartamento) {
    return $apartamento['ciudad'] === 'Valencia';
});

var_dump($todosLosApartamentosValencia);

/*
array(2) {
  [0]=>
  array(4) {
    ["precio/noche"]=>
    int(40)
    ["ciudad"]=>
    string(8) "Valencia"
    ["wifi"]=>
    bool(true)
    ["pagina web"]=>
    string(17) "https://hotel.com"
  }
  [2]=>
  array(4) {
    ["precio/noche"]=>
    int(67)
    ["ciudad"]=>
    string(8) "Valencia"
    ["wifi"]=>
    bool(false)
    ["pagina web"]=>
    string(17) "https://denia.com"
  }
}
*/

array_map (modificar)

Transforma el contenido de un array pero mantiene el número de elementos.

array_map({función}, {array});

En este ejemplo vamos a reducir el precio por noche en 1.

<?php

// Diccionario
$apartamentos = [
    [
        'precio/noche' => 40,
        'ciudad' => 'Valencia',
        'wifi' => True,
        'pagina web' => 'https://hotel.com'
    ],
    [
        'precio/noche' => 87,
        'ciudad' => 'Calpe',
        'wifi' => True,
        'pagina web' => 'https://calpe.com'
    ],
    [
        'precio/noche' => 67,
        'ciudad' => 'Valencia',
        'wifi' => False,
        'pagina web' => 'https://denia.com'
    ],
    [
        'precio/noche' => 105,
        'ciudad' => 'Benidorm',
        'wifi' => False,
        'pagina web' => 'https://benidorm.com'
    ]
];

$apartamentosMasBaratos = array_map(function ($apartamento) {
    return array_merge($apartamento, ['precio/noche' => $apartamento['precio/noche'] - 1]);
}, $apartamentos);

var_dump($apartamentosMasBaratos);
/*
array(4) {
  [0]=>
  array(4) {
    ["precio/noche"]=>
    int(39)
    ["ciudad"]=>
    string(8) "Valencia"
    ["wifi"]=>
    bool(true)
    ["pagina web"]=>
    string(17) "https://hotel.com"
  }
  [1]=>
  array(4) {
    ["precio/noche"]=>
    int(86)
    ["ciudad"]=>
    string(5) "Calpe"
    ["wifi"]=>
    bool(true)
    ["pagina web"]=>
    string(17) "https://calpe.com"
  }
  [2]=>
  array(4) {
    ["precio/noche"]=>
    int(66)
    ["ciudad"]=>
    string(8) "Valencia"
    ["wifi"]=>
    bool(false)
    ["pagina web"]=>
    string(17) "https://denia.com"
  }
  [3]=>
  array(4) {
    ["precio/noche"]=>
    int(104)
    ["ciudad"]=>
    string(8) "Benidorm"
    ["wifi"]=>
    bool(false)
    ["pagina web"]=>
    string(20) "https://benidorm.com"
  }
}
*/

array_reduce (calcular)

Obtiene un resultado a partir de un array.

array_reduce({array}, {función}, {inicial});

En este ejemplo vamos calcular cual es la media del precio por noche.

<?php

// Diccionario
$apartamentos = [
    [
        'precio/noche' => 40,
        'ciudad' => 'Valencia',
        'wifi' => True,
        'pagina web' => 'https://hotel.com'
    ],
    [
        'precio/noche' => 87,
        'ciudad' => 'Calpe',
        'wifi' => True,
        'pagina web' => 'https://calpe.com'
    ],
    [
        'precio/noche' => 67,
        'ciudad' => 'Valencia',
        'wifi' => False,
        'pagina web' => 'https://denia.com'
    ],
    [
        'precio/noche' => 105,
        'ciudad' => 'Benidorm',
        'wifi' => False,
        'pagina web' => 'https://benidorm.com'
    ]
];

$media = array_reduce($apartamentos, function ($acumulador, $apartamento) {
    return $apartamento['precio/noche'] + $acumulador;
}, 0) / count($apartamentos);

echo $media;
// 74.75
Actividad 1
  • Construye un formulario donde se pida la siguiente información: apodo, edad e imagen de perfil.
  • Al enviar muestra la información en un formato similar a Twitter o una red social. La imagen debe estar presente.
Actividad 2
  • Crea un formulario para subir un producto a una tienda: número de serie, nombre, precio e imagen.
  • Crea una página de identificación antes de entrar en los productos: nombre, contraseña 1 y contraseña 2.
  • Debe coincidir ambas contraseñas, además del nombre y contraseña con unas variables que tengamos almacenadas.
Actividad 3

Crea un formulario para subir un imagen. Al ser subida debes:

  • Redimensionarla a una anchura de 400 pixeles.
  • Mostrarla debajo del formulario.

Pro:

  • Convertirla en blanco y negro.
  • Crea un crop de la imagen para que sea cuadrada sin deformarla (400x400).

This work is under a Attribution-NonCommercial-NoDerivatives 4.0 International license.

Desafíos de programación atemporales y multiparadigmáticos

Desafíos de programación atemporales y multiparadigmáticos

Te encuentras ante un librillo de actividades, divididas en 2 niveles de dificultad. Te enfrentarás a los casos más comunes que te puedes encontrar en pruebas técnicas o aprender conceptos elementales de programación.

Buy the book

Will you buy me a coffee?

Comments

There are no comments yet.

Visitors in real time

You are alone: 🐱