Leccion 26 de 75 15 min de lectura

Funciones de Orden Superior

Las funciones de orden superior son funciones que reciben otras funciones como argumentos o que retornan funciones. PHP incluye varias funciones de este tipo muy utiles para trabajar con arrays de forma declarativa y expresiva.

¿Que son las funciones de orden superior?

Una funcion de orden superior es una funcion que cumple al menos una de estas condiciones:

  • Recibe una o más funciones como argumentos
  • Retorna una funcion como resultado
PHP
<?php

declare(strict_types=1);

// Funcion de orden superior que recibe una funcion
function aplicarATodos(array $items, callable $funcion): array
{
    $resultado = [];
    foreach ($items as $item) {
        $resultado[] = $funcion($item);
    }
    return $resultado;
}

// Uso
$numeros = [1, 2, 3, 4, 5];
$cuadrados = aplicarATodos($numeros, fn (int $n): int => $n ** 2);
print_r($cuadrados); // [1, 4, 9, 16, 25]

// Funcion de orden superior que retorna una funcion
function crearSumador(int $valor): callable
{
    return fn (int $n): int => $n + $valor;
}

$sumar10 = crearSumador(10);
echo $sumar10(5);  // 15
echo $sumar10(20); // 30

array_map: Transformar elementos

array_map aplica una funcion a cada elemento de un array y retorna un nuevo array con los resultados:

PHP
<?php

declare(strict_types=1);

$numeros = [1, 2, 3, 4, 5];

// Duplicar cada numero
$dobles = array_map(fn (int $n): int => $n * 2, $numeros);
print_r($dobles); // [2, 4, 6, 8, 10]

// Convertir a strings
$strings = array_map(fn (int $n): string => "Numero: $n", $numeros);
print_r($strings); // ['Numero: 1', 'Numero: 2', ...]

// Con arrays asociativos
$usuarios = [
    ['nombre' => 'Ana', 'email' => 'ana@example.com'],
    ['nombre' => 'Luis', 'email' => 'luis@example.com'],
];

$emails = array_map(fn (array $u): string => $u['email'], $usuarios);
print_r($emails); // ['ana@example.com', 'luis@example.com']

// Transformar estructura
$usuariosFormateados = array_map(
    fn (array $u): string => "{$u['nombre']} <{$u['email']}>",
    $usuarios
);
print_r($usuariosFormateados);
// ['Ana ', 'Luis ']

array_map con multiples arrays

PHP
<?php

declare(strict_types=1);

$nombres = ['Ana', 'Luis', 'Eva'];
$edades = [25, 30, 28];

// Combinar dos arrays elemento a elemento
$personas = array_map(
    fn (string $nombre, int $edad): array => ['nombre' => $nombre, 'edad' => $edad],
    $nombres,
    $edades
);

print_r($personas);
/*
[
    ['nombre' => 'Ana', 'edad' => 25],
    ['nombre' => 'Luis', 'edad' => 30],
    ['nombre' => 'Eva', 'edad' => 28],
]
*/

// Sumar elementos de dos arrays
$a = [1, 2, 3];
$b = [10, 20, 30];

$sumas = array_map(fn (int $x, int $y): int => $x + $y, $a, $b);
print_r($sumas); // [11, 22, 33]

array_filter: Filtrar elementos

array_filter retorna un nuevo array con los elementos que pasan una condicion:

PHP
<?php

declare(strict_types=1);

$numeros = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

// Filtrar pares
$pares = array_filter($numeros, fn (int $n): bool => $n % 2 === 0);
print_r($pares); // [1 => 2, 3 => 4, 5 => 6, 7 => 8, 9 => 10]

// Nota: array_filter preserva las claves originales
// Usa array_values si necesitas reindexar
$paresReindexados = array_values($pares);
print_r($paresReindexados); // [2, 4, 6, 8, 10]

// Filtrar mayores que 5
$mayores = array_filter($numeros, fn (int $n): bool => $n > 5);
print_r(array_values($mayores)); // [6, 7, 8, 9, 10]

// Sin callback: elimina valores falsy (0, '', null, false)
$mixto = [0, 1, '', 'hola', null, true, false];
$sinFalsy = array_filter($mixto);
print_r($sinFalsy); // [1 => 1, 3 => 'hola', 5 => true]

Filtrar con acceso a claves

PHP
<?php

declare(strict_types=1);

$datos = [
    'nombre' => 'Ana',
    'edad' => 25,
    'email' => 'ana@example.com',
    'password' => 'secreto123',
    'telefono' => '555-1234',
];

// Filtrar por clave: ARRAY_FILTER_USE_KEY
$soloNombreEmail = array_filter(
    $datos,
    fn (string $clave): bool => in_array($clave, ['nombre', 'email'], true),
    ARRAY_FILTER_USE_KEY
);
print_r($soloNombreEmail);
// ['nombre' => 'Ana', 'email' => 'ana@example.com']

// Filtrar por clave y valor: ARRAY_FILTER_USE_BOTH
$filtrado = array_filter(
    $datos,
    fn (mixed $valor, string $clave): bool =>
        $clave !== 'password' && $valor !== '',
    ARRAY_FILTER_USE_BOTH
);
print_r($filtrado);

array_reduce: Reducir a un valor

array_reduce reduce un array a un solo valor aplicando una funcion acumuladora:

PHP
<?php

declare(strict_types=1);

$numeros = [1, 2, 3, 4, 5];

// Sumar todos los elementos
$suma = array_reduce(
    $numeros,
    fn (int $acumulador, int $actual): int => $acumulador + $actual,
    0  // valor inicial
);
echo $suma; // 15

// Multiplicar todos los elementos
$producto = array_reduce(
    $numeros,
    fn (int $acc, int $n): int => $acc * $n,
    1  // valor inicial (1 para multiplicacion)
);
echo $producto; // 120

// Encontrar el maximo
$maximo = array_reduce(
    $numeros,
    fn (int $acc, int $n): int => $n > $acc ? $n : $acc,
    PHP_INT_MIN
);
echo $maximo; // 5

// Concatenar strings
$palabras = ['Hola', 'mundo', 'PHP'];
$frase = array_reduce(
    $palabras,
    fn (string $acc, string $palabra): string =>
        $acc === '' ? $palabra : "$acc $palabra",
    ''
);
echo $frase; // 'Hola mundo PHP'

Casos avanzados de reduce

PHP
<?php

declare(strict_types=1);

$productos = [
    ['nombre' => 'Laptop', 'precio' => 999, 'cantidad' => 1],
    ['nombre' => 'Mouse', 'precio' => 25, 'cantidad' => 2],
    ['nombre' => 'Teclado', 'precio' => 75, 'cantidad' => 1],
];

// Calcular total del carrito
$total = array_reduce(
    $productos,
    fn (float $acc, array $p): float => $acc + ($p['precio'] * $p['cantidad']),
    0.0
);
echo "Total: $total"; // Total: 1124

// Agrupar por categoria
$items = [
    ['tipo' => 'fruta', 'nombre' => 'manzana'],
    ['tipo' => 'verdura', 'nombre' => 'zanahoria'],
    ['tipo' => 'fruta', 'nombre' => 'pera'],
    ['tipo' => 'verdura', 'nombre' => 'brocoli'],
];

$agrupados = array_reduce(
    $items,
    function (array $acc, array $item): array {
        $tipo = $item['tipo'];
        if (!isset($acc[$tipo])) {
            $acc[$tipo] = [];
        }
        $acc[$tipo][] = $item['nombre'];
        return $acc;
    },
    []
);

print_r($agrupados);
/*
[
    'fruta' => ['manzana', 'pera'],
    'verdura' => ['zanahoria', 'brocoli'],
]
*/

// Contar ocurrencias
$letras = ['a', 'b', 'a', 'c', 'b', 'a'];

$conteo = array_reduce(
    $letras,
    function (array $acc, string $letra): array {
        $acc[$letra] = ($acc[$letra] ?? 0) + 1;
        return $acc;
    },
    []
);

print_r($conteo); // ['a' => 3, 'b' => 2, 'c' => 1]

usort, uasort, uksort: Ordenar con funciones

Estas funciones permiten ordenar arrays usando una funcion de comparacion personalizada:

PHP
<?php

declare(strict_types=1);

// usort: ordena por valores, reindexa
$usuarios = [
    ['nombre' => 'Ana', 'edad' => 28],
    ['nombre' => 'Luis', 'edad' => 35],
    ['nombre' => 'Eva', 'edad' => 22],
];

// Ordenar por edad ascendente
usort($usuarios, fn (array $a, array $b): int => $a['edad'] <=> $b['edad']);
// Eva(22), Ana(28), Luis(35)

// Ordenar por edad descendente
usort($usuarios, fn (array $a, array $b): int => $b['edad'] <=> $a['edad']);
// Luis(35), Ana(28), Eva(22)

// uasort: ordena por valores, preserva claves
$puntuaciones = [
    'jugador1' => 150,
    'jugador2' => 200,
    'jugador3' => 100,
];

uasort($puntuaciones, fn (int $a, int $b): int => $b <=> $a);
print_r($puntuaciones);
// ['jugador2' => 200, 'jugador1' => 150, 'jugador3' => 100]

// uksort: ordena por claves
$datos = [
    'zebra' => 1,
    'alfa' => 2,
    'beta' => 3,
];

uksort($datos, fn (string $a, string $b): int => $a <=> $b);
print_r($datos); // ['alfa' => 2, 'beta' => 3, 'zebra' => 1]

array_walk: Modificar elementos

array_walk aplica una funcion a cada elemento del array original, modificandolo en lugar:

PHP
<?php

declare(strict_types=1);

$precios = [100, 200, 300];

// Aplicar descuento del 10% a cada precio
// Nota: el primer parametro se pasa por referencia
array_walk($precios, function (int|float &$precio): void {
    $precio = $precio * 0.9;
});

print_r($precios); // [90, 180, 270]

// Con clave disponible
$productos = ['laptop' => 999, 'mouse' => 25, 'teclado' => 75];

array_walk($productos, function (int &$precio, string $nombre): void {
    echo "$nombre: \$$precio\n";
    $precio = (int) ($precio * 1.21); // Añadir IVA
});

print_r($productos); // ['laptop' => 1208, 'mouse' => 30, 'teclado' => 90]

// Con dato adicional
$valores = [10, 20, 30];
$factor = 5;

array_walk($valores, function (int &$valor, int $key, int $factor): void {
    $valor = $valor * $factor;
}, $factor);

print_r($valores); // [50, 100, 150]
array_map vs array_walk

array_map retorna un nuevo array y no modifica el original. array_walk modifica el array original y retorna true/false.

Combinar funciones

El verdadero poder de las funciones de orden superior esta en combinarlas:

PHP
<?php

declare(strict_types=1);

$pedidos = [
    ['cliente' => 'Ana', 'total' => 150, 'estado' => 'completado'],
    ['cliente' => 'Luis', 'total' => 50, 'estado' => 'pendiente'],
    ['cliente' => 'Eva', 'total' => 200, 'estado' => 'completado'],
    ['cliente' => 'Carlos', 'total' => 75, 'estado' => 'cancelado'],
    ['cliente' => 'Maria', 'total' => 300, 'estado' => 'completado'],
];

// Objetivo: suma de pedidos completados mayores a 100

// Paso 1: Filtrar completados
$completados = array_filter(
    $pedidos,
    fn (array $p): bool => $p['estado'] === 'completado'
);

// Paso 2: Filtrar mayores a 100
$mayores100 = array_filter(
    $completados,
    fn (array $p): bool => $p['total'] > 100
);

// Paso 3: Extraer totales
$totales = array_map(fn (array $p): int => $p['total'], $mayores100);

// Paso 4: Sumar
$suma = array_sum($totales);

echo "Total: $suma"; // 650

// Version compacta (encadenada)
$sumaCompacta = array_sum(
    array_map(
        fn (array $p): int => $p['total'],
        array_filter(
            $pedidos,
            fn (array $p): bool =>
                $p['estado'] === 'completado' && $p['total'] > 100
        )
    )
);

echo "Total: $sumaCompacta"; // 650

Crear tus propias funciones de orden superior

PHP
<?php

declare(strict_types=1);

// Funcion que encuentra el primer elemento que cumple condicion
function encontrarPrimero(array $items, callable $condicion): mixed
{
    foreach ($items as $item) {
        if ($condicion($item)) {
            return $item;
        }
    }
    return null;
}

$usuarios = [
    ['id' => 1, 'nombre' => 'Ana', 'activo' => false],
    ['id' => 2, 'nombre' => 'Luis', 'activo' => true],
    ['id' => 3, 'nombre' => 'Eva', 'activo' => true],
];

$primerActivo = encontrarPrimero(
    $usuarios,
    fn (array $u): bool => $u['activo']
);
print_r($primerActivo); // ['id' => 2, 'nombre' => 'Luis', ...]

// Funcion que verifica si todos los elementos cumplen condicion
function todos(array $items, callable $condicion): bool
{
    foreach ($items as $item) {
        if (!$condicion($item)) {
            return false;
        }
    }
    return true;
}

// Funcion que verifica si algun elemento cumple condicion
function alguno(array $items, callable $condicion): bool
{
    foreach ($items as $item) {
        if ($condicion($item)) {
            return true;
        }
    }
    return false;
}

$numeros = [2, 4, 6, 8];

var_dump(todos($numeros, fn (int $n): bool => $n % 2 === 0)); // true
var_dump(todos($numeros, fn (int $n): bool => $n > 5));       // false
var_dump(alguno($numeros, fn (int $n): bool => $n > 5));      // true

Ejemplo practico: Pipeline de procesamiento

PHP
<?php

declare(strict_types=1);

// Funcion pipe que ejecuta funciones en secuencia
function pipe(mixed $valor, callable ...$funciones): mixed
{
    return array_reduce(
        $funciones,
        fn (mixed $acc, callable $fn): mixed => $fn($acc),
        $valor
    );
}

// Transformadores individuales
$trimear = fn (string $s): string => trim($s);
$minusculas = fn (string $s): string => strtolower($s);
$sinEspaciosDobles = fn (string $s): string => preg_replace('/\s+/', ' ', $s) ?? $s;
$capitalizar = fn (string $s): string => ucwords($s);

// Usar el pipeline
$entrada = '   HOLA    MUNDO   PHP   ';
$resultado = pipe(
    $entrada,
    $trimear,
    $minusculas,
    $sinEspaciosDobles,
    $capitalizar
);

echo $resultado; // 'Hola Mundo Php'

// Pipeline para numeros
$duplicar = fn (int $n): int => $n * 2;
$sumar10 = fn (int $n): int => $n + 10;
$alCuadrado = fn (int $n): int => $n ** 2;

$numero = pipe(5, $duplicar, $sumar10, $alCuadrado);
echo $numero; // 400 ((5 * 2 + 10) ^ 2)

Ejemplo practico: Procesador de datos

PHP
<?php

declare(strict_types=1);

// Datos de ventas
$ventas = [
    ['fecha' => '2024-01-15', 'producto' => 'A', 'cantidad' => 10, 'precio' => 25.00],
    ['fecha' => '2024-01-15', 'producto' => 'B', 'cantidad' => 5, 'precio' => 50.00],
    ['fecha' => '2024-01-16', 'producto' => 'A', 'cantidad' => 8, 'precio' => 25.00],
    ['fecha' => '2024-01-16', 'producto' => 'C', 'cantidad' => 3, 'precio' => 100.00],
    ['fecha' => '2024-01-17', 'producto' => 'B', 'cantidad' => 12, 'precio' => 50.00],
];

// Calcular total por venta
$ventasConTotal = array_map(
    fn (array $v): array => [
        ...$v,
        'total' => $v['cantidad'] * $v['precio'],
    ],
    $ventas
);

// Filtrar ventas mayores a 200
$ventasGrandes = array_filter(
    $ventasConTotal,
    fn (array $v): bool => $v['total'] > 200
);

// Ordenar por total descendente
usort($ventasGrandes, fn (array $a, array $b): int => (int) ($b['total'] - $a['total']));

// Calcular estadisticas
$totalVentas = array_reduce(
    $ventasConTotal,
    fn (float $acc, array $v): float => $acc + $v['total'],
    0.0
);

$promedioVenta = $totalVentas / count($ventasConTotal);

// Agrupar por producto
$porProducto = array_reduce(
    $ventasConTotal,
    function (array $acc, array $venta): array {
        $producto = $venta['producto'];
        if (!isset($acc[$producto])) {
            $acc[$producto] = ['cantidad' => 0, 'total' => 0.0];
        }
        $acc[$producto]['cantidad'] += $venta['cantidad'];
        $acc[$producto]['total'] += $venta['total'];
        return $acc;
    },
    []
);

echo "Total ventas: \$$totalVentas\n";      // Total ventas: $1750
echo "Promedio: \$$promedioVenta\n";        // Promedio: $350
print_r($porProducto);
/*
[
    'A' => ['cantidad' => 18, 'total' => 450],
    'B' => ['cantidad' => 17, 'total' => 850],
    'C' => ['cantidad' => 3, 'total' => 300],
]
*/

Ejercicios

Ejercicio 1: Transformar con array_map

Dado un array de nombres en minusculas ['ana', 'luis', 'eva'], usa array_map para convertirlos a mayusculas. Usa una arrow function.

Ver solucion
PHP
<?php
declare(strict_types=1);

$nombres = ['ana', 'luis', 'eva'];

$nombresMayusculas = array_map(
    fn (string $nombre): string => strtoupper($nombre),
    $nombres
);

print_r($nombresMayusculas);
// ['ANA', 'LUIS', 'EVA']

Ejercicio 2: Filtrar con array_filter

Tienes un array de productos: [['nombre' => 'A', 'stock' => 5], ['nombre' => 'B', 'stock' => 0], ['nombre' => 'C', 'stock' => 12]]. Usa array_filter para quedarte solo con los productos que tienen stock mayor a 0.

Ver solucion
PHP
<?php
declare(strict_types=1);

$productos = [
    ['nombre' => 'A', 'stock' => 5],
    ['nombre' => 'B', 'stock' => 0],
    ['nombre' => 'C', 'stock' => 12],
];

$conStock = array_filter(
    $productos,
    fn (array $p): bool => $p['stock'] > 0
);

print_r(array_values($conStock));
// [['nombre' => 'A', 'stock' => 5], ['nombre' => 'C', 'stock' => 12]]

Ejercicio 3: Reducir con array_reduce

Dado un array de numeros [10, 20, 30, 40], usa array_reduce para calcular el producto de todos los elementos (multiplicarlos entre si). El resultado debe ser 240000.

Ver solucion
PHP
<?php
declare(strict_types=1);

$numeros = [10, 20, 30, 40];

$producto = array_reduce(
    $numeros,
    fn (int $acc, int $n): int => $acc * $n,
    1  // valor inicial para multiplicacion
);

echo $producto; // 240000

¿Te está gustando el curso?

Tenemos cursos premium con proyectos reales y soporte personalizado.

Descubrir cursos premium