First class callable syntax

1. Introdução à First Class Callable Syntax

A First Class Callable Syntax (sintaxe de callable de primeira classe) foi introduzida no PHP 8.1 como uma forma elegante e concisa de criar closures a partir de funções existentes. Antes dessa feature, criar um callable a partir de uma função exigia escrever closures anônimas verbosas ou usar strings com nomes de funções, o que dificultava a legibilidade e a inferência de tipos.

A motivação principal é reduzir a verbosidade e melhorar a legibilidade do código. Em vez de escrever:

$closure = function($s) { return strlen($s); };

Podemos simplesmente escrever:

$closure = strlen(...);

Essa sintaxe transforma qualquer função ou método em um objeto Closure de primeira classe, permitindo que seja passado como argumento, armazenado em variáveis ou usado em funções de ordem superior sem a necessidade de wrappers desnecessários.

2. Sintaxe Básica e Exemplos

A sintaxe básica consiste em adicionar (...) após o nome da função ou método. Isso cria um callable que pode ser invocado posteriormente.

Exemplo com função nomeada:

$strlen = strlen(...);
echo $strlen('Hello World'); // 11

Comparação com closure tradicional:

// Antes (PHP < 8.1)
$strlenOld = function($s) { return strlen($s); };

// Depois (PHP 8.1+)
$strlenNew = strlen(...);

Callables a partir de métodos estáticos:

class MathUtils {
    public static function square(int $n): int {
        return $n * $n;
    }
}

$square = MathUtils::square(...);
echo $square(5); // 25

Callables a partir de métodos de instância:

class Greeter {
    public function greet(string $name): string {
        return "Hello, $name!";
    }
}

$greeter = new Greeter();
$greetMethod = $greeter->greet(...);
echo $greetMethod('Alice'); // Hello, Alice!

Callables a partir de funções anônimas:

$double = function(int $x): int { return $x * 2; };
$doubleCallable = $double(...);
echo $doubleCallable(10); // 20

3. Funcionamento Interno e Tipos

Quando você usa a sintaxe strlen(...), o PHP internamente cria um objeto da classe Closure. Essa closure mantém uma referência à função original e pode ser invocada como qualquer outra closure.

Verificação de tipo:

$callable = strlen(...);
var_dump($callable instanceof Closure); // bool(true)
var_dump(is_callable($callable)); // bool(true)

Inferência de assinatura:

O PHP infere automaticamente os parâmetros e o tipo de retorno da closure a partir da função original:

$trim = trim(...);
$reflection = new ReflectionFunction($trim);
echo $reflection->getNumberOfParameters(); // 2 (string, characters)

Exemplo com call_user_func:

$upper = strtoupper(...);
echo call_user_func($upper, 'php'); // PHP

4. Uso em Funções de Ordem Superior

A First Class Callable Syntax brilha especialmente quando usada com funções de ordem superior como array_map, array_filter e array_reduce.

Exemplo com array_map:

$nomes = ['alice', 'bob', 'charlie'];

// Sintaxe tradicional
$resultadoOld = array_map(function($n) { return strtoupper($n); }, $nomes);

// Com First Class Callable Syntax
$resultadoNew = array_map(strtoupper(...), $nomes);

print_r($resultadoNew);
// Array ( [0] => ALICE [1] => BOB [2] => CHARLIE )

Exemplo com array_filter:

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

// Filtrar números pares
$pares = array_filter($numeros, function($n) { return $n % 2 === 0; });

// Usando uma função auxiliar
function isPar(int $n): bool {
    return $n % 2 === 0;
}

$pares = array_filter($numeros, isPar(...));
print_r($pares); // Array ( [1] => 2 [3] => 4 [5] => 6 )

Exemplo com array_reduce:

$valores = [10, 20, 30];

function soma(int $acc, int $val): int {
    return $acc + $val;
}

$total = array_reduce($valores, soma(...), 0);
echo $total; // 60

Pipeline de processamento de dados:

$dados = ['  alice ', 'BOB', '  CHARLIE  '];

$processados = array_map(
    strtolower(...),
    array_map(
        trim(...),
        $dados
    )
);

print_r($processados);
// Array ( [0] => alice [1] => bob [2] => charlie )

5. Limitações e Casos de Borda

Restrições com $this:

A sintaxe não pode ser usada diretamente com $this->method(...) dentro de um contexto de objeto. Você precisa primeiro armazenar o método em uma variável:

class UserService {
    public function formatName(string $name): string {
        return ucfirst(strtolower($name));
    }

    public function processNames(array $names): array {
        // Isso NÃO funciona:
        // return array_map($this->formatName(...), $names);

        // Isso funciona:
        $formatter = $this->formatName(...);
        return array_map($formatter, $names);
    }
}

Callables com parâmetros variádicos:

function somarTodos(int ...$numeros): int {
    return array_sum($numeros);
}

$soma = somarTodos(...);
echo $soma(1, 2, 3, 4); // 10

Comportamento com funções internas vs funções definidas pelo usuário:

A sintaxe funciona tanto com funções internas do PHP quanto com funções definidas pelo usuário, mas há pequenas diferenças de performance:

// Função interna
$internal = array_map(...);

// Função definida pelo usuário
function meuMap(callable $fn, array $arr): array {
    return array_map($fn, $arr);
}
$userDefined = meuMap(...);

Diferenças entre $fn(...) e Closure::fromCallable('fn'):

// Sintaxe First Class Callable
$callable1 = strlen(...);

// Equivalente explícito
$callable2 = Closure::fromCallable('strlen');

var_dump($callable1 == $callable2); // bool(true)

A principal diferença é que Closure::fromCallable() aceita strings e arrays como callable, enquanto a sintaxe (...) só funciona com identificadores de função/método.

6. Comparação com Alternativas

Closures tradicionais:

// Antes: verboso
$resultado = array_map(function($s) { return strtoupper($s); }, $array);

// Depois: conciso
$resultado = array_map(strtoupper(...), $array);

Sintaxe de string com nomes de função:

// String - sem type safety
$resultado = array_map('strtoupper', $array);

// First Class - com type safety
$resultado = array_map(strtoupper(...), $array);

Arrays de callable:

class Logger {
    public function log(string $msg): void { echo $msg; }
}

$logger = new Logger();

// Array tradicional
$callable = [$logger, 'log'];

// First Class Callable Syntax
$callable = $logger->log(...);

Closure::fromCallable() explícito:

// Explícito
$callable = Closure::fromCallable('strtoupper');

// First Class Callable Syntax
$callable = strtoupper(...);

7. Boas Práticas e Aplicações no Mundo Real

Quando usar:

  1. Funções de ordem superior: Sempre que precisar passar uma função como argumento para array_map, array_filter, etc.
  2. Event listeners: Em sistemas de eventos, para criar handlers concisos.
  3. Composição de funções: Para criar pipelines de processamento de dados.
  4. Refatoração: Substituir closures anônimas simples que apenas chamam uma função.

Refatoração de código legado:

// Antes
$usuarios = array_map(function($user) {
    return strtolower($user['email']);
}, $users);

// Depois
function extractEmail(array $user): string {
    return strtolower($user['email']);
}
$usuarios = array_map(extractEmail(...), $users);

Exemplo em framework moderno (Laravel-style):

$pedidos = collect($pedidos)
    ->filter(fn($p) => $p['status'] === 'pendente')
    ->map(fn($p) => $p['total'])
    ->map(fn($t) => $t * 1.1) // imposto 10%
    ->map(fn($t) => number_format($t, 2));

// Com First Class Callable Syntax
function aplicarImposto(float $valor): float {
    return $valor * 1.1;
}

function formatarMoeda(float $valor): string {
    return number_format($valor, 2);
}

$pedidos = collect($pedidos)
    ->filter(fn($p) => $p['status'] === 'pendente')
    ->map(fn($p) => $p['total'])
    ->map(aplicarImposto(...))
    ->map(formatarMoeda(...));

Performance:

A First Class Callable Syntax tem performance comparável a Closure::fromCallable() e é geralmente mais rápida que closures anônimas que apenas chamam funções, pois elimina a sobrecarga da closure wrapper.

8. Conclusão e Referências

A First Class Callable Syntax é uma adição elegante ao PHP 8.1+ que melhora significativamente a legibilidade e concisão do código. Ela permite tratar funções como cidadãs de primeira classe de forma mais natural, reduzindo a verbosidade desnecessária e mantendo a segurança de tipos.

Os benefícios principais incluem:
- Redução de boilerplate
- Melhor legibilidade em funções de ordem superior
- Type safety mantido
- Performance competitiva

Em conjunto com outros recursos do PHP 8.1+ como Fibers, readonly properties e atributos nomeados, a First Class Callable Syntax contribui para um PHP mais moderno e expressivo.

Referências