Closure Optimizations no PHP 8.6: a performance que você ganha só subindo de versão

Introdução

Nos últimos dois dias o assunto foi concorrência: a Polling API e o True Async, RFCs que prometem mudar como o PHP executa I/O. São features grandes, ambiciosas — e que exigem você reescrever código para colher o benefício.

Hoje o tema é o oposto disso, e por isso talvez mais imediato: a RFC Closure Optimizations foi aceita para o PHP 8.6, com placar de 24 a favor, 0 contra e 1 abstenção. É uma das otimizações mais silenciosas e mais democráticas do ciclo do 8.6, porque você não muda uma linha de código para ganhar com ela. Sobe a versão, e o ganho cai no colo.

Parece pequeno. Não é. Closures são onipresentes em backend PHP moderno — e elas têm um custo escondido que poucos devs medem. Vamos entender exatamente qual é esse custo e como o 8.6 o elimina.

Contexto: closures não são de graça

Toda vez que você escreve uma closure no PHP, o engine cria um objeto Closure em runtime. Até aí, esperado. O problema é o que vem junto quando a closure é declarada dentro de um método de classe:

class RelatorioService
{
    public function gerar(): Closure
    {
        // Esta closure NÃO usa $this...
        return function (array $linha) {
            return strtoupper($linha['nome']);
        };
    }
}

Mesmo que a closure acima não use $this, o PHP — até a versão 8.5 — vincula implicitamente o objeto $this a ela. Esse binding cria um ciclo de referência: a closure aponta para o objeto, e enquanto a closure viver, o objeto não pode ser coletado de forma trivial. Isso aumenta a pressão sobre o garbage collector e adiciona alocação de memória que não tinha por que existir.

Multiplique isso por um framework. Blade compila templates em closures. Collection::map(), filter(), reduce(), each() recebem closures. Pipelines de middleware são closures. O service container resolve bindings via closures. Em uma única requisição Laravel, você cria milhares delas.

O Problema Real

A própria RFC traz o número que dói: instrumentando templates do Laravel, das 3.637 closures que seriam criadas, 2.384 não precisavam existir como closures com estado — cerca de 65%. Cada uma dessas era uma alocação e um potencial ciclo de referência desnecessário.

O ponto não é “3% de ganho num benchmark”. O ponto é que esse é um custo que você paga em toda requisição, em toda aplicação PHP orientada a objetos, sem nunca ter optado por ele. É overhead estrutural, não um problema do seu código.

A Solução: duas otimizações transparentes

A RFC introduz duas mudanças no engine. Nenhuma delas muda a sintaxe — você escreve PHP exatamente como escreve hoje.

1. Static closure inference

Se a closure não referencia $this, o engine a converte automaticamente para uma closure estática:

class RelatorioService
{
    public function gerar(): Closure
    {
        // PHP 8.6: inferida como `static` automaticamente.
        // Sem binding de $this, sem ciclo de referência.
        return function (array $linha) {
            return strtoupper($linha['nome']);
        };
    }
}

Antes do 8.6, para obter esse comportamento você precisava lembrar de escrever static function, manualmente, em toda closure que não usasse $this. Quase ninguém fazia. Agora o engine faz por você, e de forma segura: se a closure usar $this, nada muda.

2. Stateless closure caching

Closures estáticas que não capturam nenhuma variável são reaproveitadas entre chamadas, em vez de instanciadas de novo:

function fabricaDeCallback(): Closure
{
    return static function () {
        return 'sempre o mesmo';
    };
}

// PHP 8.6: mesma instância reutilizada
var_dump(fabricaDeCallback() === fabricaDeCallback()); // true

Como a closure não tem estado (não captura nada), não há razão para o engine criar um objeto novo a cada chamada. Ele cacheia e devolve a mesma instância. Menos alocação, menos trabalho para o GC.

Compatibilidade

Houve o cuidado de não quebrar nada. Closure::bind() e bindTo() passam a aceitar (mas descartar) um binding de objeto quando aplicados a uma closure inferida como estática. Ou seja: código que tentava religar $this numa closure que o engine tornou estática não explode — o binding é simplesmente ignorado, mantendo a semântica esperada de uma static closure.

Impacto Técnico

Para apps Laravel e Symfony

Frameworks são os maiores beneficiários, e não por acaso o benchmark da RFC usou o Laravel. Qualquer aplicação que dependa intensamente de Collection, pipelines, eventos e service container vai ver menos alocação de objetos Closure por requisição. O ganho medido foi de aproximadamente 3% em cenário real de templates — modesto isoladamente, relevante quando multiplicado por milhões de requisições/dia e por custo de infra.

Para quem roda worker mode

Aqui há uma sinergia que vale destacar. Em FrankenPHP ou RoadRunner, o processo fica vivo entre requisições. Menos ciclos de referência e menos pressão de GC significam um worker que degrada mais devagar ao longo do tempo — menos memória acumulada, menos pausa de coleta. Otimização de alocação importa mais, não menos, quando o processo é de vida longa.

O que NÃO muda

Importante calibrar a expectativa: isso não transforma PHP lento em PHP rápido. É uma otimização de alocação e memória, não de CPU bruta. Se o seu gargalo é uma query N+1 ou uma chamada HTTP síncrona de 200ms, Closure Optimizations não vai resolver. O valor está em reduzir o overhead estrutural — o imposto invisível que você pagava sem saber.

Como se preparar

Não há nada para “adotar”. A recomendação prática é:

  1. Acompanhe o calendário do 8.6: Alpha 1 em 02/07/2026, feature freeze + Beta 1 em 13/08/2026, GA alvo em 19/11/2026.
  2. Teste em ambiente isolado quando o Alpha sair, especialmente se você tem código que faz bindTo() em closures de forma criativa — é o único ponto com mudança de semântica perceptível.
  3. Se você escreve bibliotecas: vale revisar se há dependência implícita de $this em closures que você esperava manter vinculadas. Na dúvida, seja explícito.

E um hábito que essa RFC reforça: escreva static function quando a closure não usa $this. O 8.6 vai inferir isso, mas ser explícito documenta a intenção e funciona em versões anteriores

Polling API e True Async são o futuro grande e barulhento do PHP. Closure Optimizations é o presente discreto: uma melhoria de performance que chega para todo mundo, em toda aplicação, sem custo de migração e sem hype. Com 24 votos a favor e 0 contra, é também um raro caso de consenso total no processo de RFC.

Para o dev backend brasileiro, a lição é dupla. A primeira, prática: subir para o 8.6 quando estabilizar vai ser, de novo, a otimização de melhor relação custo-benefício que existe. A segunda, conceitual: closures têm custo, $this em closure tem custo, e entender o que o engine faz por baixo dos panos é o que separa quem escreve PHP de quem escreve PHP rápido.


Fontes