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: <B>HOLA</B>
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]
