Closures y arrow functions en PHP: funciones de primera clase, bind y bindTo

En PHP, las funciones son ciudadanos de primera clase: se pueden asignar a variables, pasarse como argumentos y devolverse desde otras funciones. Los closures (funciones anónimas) y las arrow functions de PHP 7.4 son las dos formas principales de crear funciones inline, cada una con su propio mecanismo de captura de variables del scope circundante.

Closures básicos: funciones anónimas

<?php
// Asignar un closure a una variable
$saludar = function (string $nombre): string {
    return "Hola, $nombre";
};

echo $saludar('Ana'); // Hola, Ana

// Como argumento de función
$numeros = [3, 1, 4, 1, 5, 9, 2, 6];
usort($numeros, function (int $a, int $b): int {
    return $a <=> $b; // operador spaceship
});
// [1, 1, 2, 3, 4, 5, 6, 9]
?>

Capturar variables con use

<?php
$multiplicador = 3;

// Captura por valor (copia en el momento de definir el closure)
$porValor = function (int $n) use ($multiplicador): int {
    return $n * $multiplicador;
};

$multiplicador = 10; // cambiar la variable original no afecta al closure
echo $porValor(5); // 15 (usa el valor 3 capturado al definir)

// Captura por referencia (&)
$contador = 0;
$incrementar = function () use (&$contador): void {
    $contador++;
};

$incrementar();
$incrementar();
echo $contador; // 2 (se modifica la variable original)
?>

Arrow functions de PHP 7.4

Las arrow functions (fn) capturan automáticamente todas las variables del scope exterior por valor, sin necesitar use:

<?php
$iva = 0.21;

// Closure con use
$precioConIva1 = function (float $base) use ($iva): float {
    return $base * (1 + $iva);
};

// Arrow function: captura $iva automáticamente
$precioConIva2 = fn(float $base): float => $base * (1 + $iva);

echo $precioConIva2(100.0); // 121.0

// Muy útil en callbacks de array_map, array_filter, usort
$precios = [10.0, 25.0, 50.0, 100.0];
$conIva = array_map(fn(float $p): float => $p * (1 + $iva), $precios);
// [12.1, 30.25, 60.5, 121.0]

$caros = array_filter($precios, fn(float $p): bool => $p > 20.0);
// [25.0, 50.0, 100.0]
?>

Closures como callbacks en funciones de array

<?php
$usuarios = [
    ['nombre' => 'Ana',  'edad' => 30, 'activo' => true],
    ['nombre' => 'Luis', 'edad' => 17, 'activo' => true],
    ['nombre' => 'Eva',  'edad' => 25, 'activo' => false],
    ['nombre' => 'Marcos', 'edad' => 22, 'activo' => true],
];

// Filtrar y transformar con arrow functions
$nombresAdultos = array_map(
    fn(array $u): string => $u['nombre'],
    array_filter(
        $usuarios,
        fn(array $u): bool => $u['activo'] && $u['edad'] >= 18
    )
);

echo implode(', ', $nombresAdultos); // Ana, Marcos

// Ordenar por edad descendente
usort($usuarios, fn(array $a, array $b): int => $b['edad'] <=> $a['edad']);
?>

Closure::bind y bindTo: vincular a objetos

<?php
class Caja
{
    private string $contenido = 'secreto';
}

// Vincular un closure a una instancia para acceder a propiedades privadas
$leer = Closure::bind(
    function (): string { return $this->contenido; },
    new Caja(),
    Caja::class  // contexto de la clase
);

echo $leer(); // secreto

// Útil en frameworks para inyectar comportamiento en objetos
$modificar = function (string $nuevo): void {
    $this->contenido = $nuevo;
};

$closure = Closure::bind($modificar, new Caja(), Caja::class);
$caja = new Caja();
$cerrado = $closure->bindTo($caja, $caja);
// bindTo() crea una nueva copia vinculada a $caja
?>

Closures como estrategias de configuración

<?php
class Coleccion
{
    public function __construct(private array $items) {}

    public function filtrar(Closure $predicado): static
    {
        return new static(array_values(array_filter($this->items, $predicado)));
    }

    public function transformar(Closure $fn): static
    {
        return new static(array_map($fn, $this->items));
    }

    public function toArray(): array { return $this->items; }
}

$resultado = (new Coleccion([1, 2, 3, 4, 5, 6, 7, 8]))
    ->filtrar(fn(int $n): bool => $n % 2 === 0)  // pares: [2,4,6,8]
    ->transformar(fn(int $n): int => $n ** 2)     // cuadrados: [4,16,36,64]
    ->toArray();
?>

La documentación oficial de funciones anónimas en PHP y la de arrow functions detallan las diferencias de captura, los tipos de retorno implícitos y las restricciones sobre el uso de yield dentro de closures.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP