First-class callables en PHP 8.1: sintaxis ..., Closure::fromCallable y callbacks modernos

PHP 8.1 introdujo la sintaxis de first-class callables: strlen(...) en lugar de 'strlen' o Closure::fromCallable('strlen'). Permite obtener una Closure de cualquier función, método estático o método de instancia usando los tres puntos (...) como único argumento. El resultado es seguro ante renombrado, reconocido por el análisis estático y completamente equivalente a una closure escrita a mano.

Sintaxis básica

<?php
// Antes (PHP < 8.1)
$fn = Closure::fromCallable('strlen');
$fn = Closure::fromCallable(['MiClase', 'metodoEstatico']);

// Desde PHP 8.1
$fn = strlen(...);
$fn = MiClase::metodoEstatico(...);
$fn = $objeto->metodoInstancia(...);

echo $fn('Hola'); // 4

Ejemplo 1: array_map con funciones nativas

<?php
// Antes
$longitudes = array_map('strlen', ['PHP', 'JavaScript', 'Go']);

// PHP 8.1: más seguro ante renombrado de funciones y reconocido por phpstan/psalm
$longitudes = array_map(strlen(...), ['PHP', 'JavaScript', 'Go']);
print_r($longitudes); // [3, 10, 2]

// Con métodos estáticos
class Sanitizador {
    public static function limpiar(string $s): string {
        return trim(strip_tags($s));
    }
}

$limpios = array_map(Sanitizador::limpiar(...), $entradas);

Ejemplo 2: usort con métodos de instancia

<?php
class Ordenador {
    public function __construct(private string $campo) {}

    public function comparar(array $a, array $b): int {
        return $a[$this->campo] <=> $b[$this->campo];
    }
}

$productos = [
    ['nombre' => 'Ratón',   'precio' => 25.0],
    ['nombre' => 'Monitor', 'precio' => 349.0],
    ['nombre' => 'Teclado', 'precio' => 49.0],
];

$ordenador = new Ordenador('precio');
usort($productos, $ordenador->comparar(...));

// Resultado ordenado por precio ascendente
foreach ($productos as $p) {
    echo "{$p['nombre']}: {$p['precio']}n";
}
// Ratón: 25
// Teclado: 49
// Monitor: 349

Ejemplo 3: composición de funciones

<?php
function componer(callable ...$fns): Closure {
    return function ($valor) use ($fns) {
        return array_reduce(
            array_reverse($fns),
            fn($carry, $fn) => $fn($carry),
            $valor
        );
    };
}

$procesar = componer(
    strtoupper(...),
    trim(...),
    htmlspecialchars(...)
);

echo $procesar('  <b>hola</b>  ');
// <B>HOLA</B>  ?  (primero htmlspecialchars, luego trim, luego strtoupper)
// Resultado: &LT;B&GT;HOLA&LT;/B&GT;

Ejemplo 4: pipeline de validaciones

<?php
class Validador {
    private array $reglas = [];

    public function agregar(callable $regla): static {
        $this->reglas[] = $regla;
        return $this;
    }

    public function validar(mixed $valor): bool {
        foreach ($this->reglas as $regla) {
            if (!$regla($valor)) return false;
        }
        return true;
    }
}

function esEntero(mixed $v): bool  { return is_int($v); }
function esPositivo(mixed $v): bool { return $v > 0; }
function menorDe100(mixed $v): bool { return $v < 100; }

$validador = (new Validador())
    ->agregar(esEntero(...))
    ->agregar(esPositivo(...))
    ->agregar(menorDe100(...));

var_dump($validador->validar(42));   // true
var_dump($validador->validar(-5));   // false
var_dump($validador->validar(200));  // false
var_dump($validador->validar("42")); // false

Diferencias con Closure::fromCallable()

  • La sintaxis fn(...) es más corta y más legible.
  • Ambas producen exactamente el mismo tipo: una instancia de Closure.
  • La nueva sintaxis no requiere el nombre como string, por lo que un renombrado de función detectado por el IDE rompe el código en compilación, no en runtime.
  • Herramientas como PHPStan y Psalm entienden fn(...) y pueden inferir los tipos de los parámetros y el retorno, algo que con strings no es posible.

Limitaciones

La sintaxis ... solo funciona para obtener la closure, no para llamar a la función. strlen(...)('Hola') también funciona (crea la closure y la llama inmediatamente), pero en ese caso es preferible llamar directamente strlen('Hola').

<?php
// Esto funciona pero no tiene sentido; llama directamente
$resultado = strlen(...)('Hola');  // 4

// El uso correcto es guardarla o pasarla
$fn = strlen(...);
$resultados = array_map($fn, ['a', 'bb', 'ccc']); // [1, 2, 3]

COMPARTE ESTE ARTÍCULO

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