O PHP 8.5 saiu em novembro de 2025 e trouxe uma safra boa de features. Não é um release que quebra tudo, mas tem pelo menos três adições que mudam a forma de escrever código no dia a dia: o pipe operator, o clone with e um par de funções de array que deviam existir desde sempre.

Este post passa por cada novidade com exemplos de código que você pode copiar e testar. Sem teoria demais, sem hype. O objetivo é simples: você termina de ler sabendo o que vale migrar agora e o que pode esperar.

O pipe operator e o fim das variáveis intermediárias

O pipe operator (|>) resolve um problema antigo: encadear chamadas de função sem aninhar parênteses nem criar variáveis temporárias. A ideia é que o resultado da expressão à esquerda vira o argumento da função à direita.

Antes do 8.5, transformar uma string passava por algo assim:

$input = '  Hello World  ';
$result = str_split(strtoupper(trim($input)));

A leitura é de dentro para fora. Com três funções já fica confuso. Com cinco, vira pesadelo. A alternativa era criar variáveis intermediárias:

$trimmed = trim($input);
$uppercased = strtoupper($trimmed);
$result = str_split($uppercased);

Funciona, mas polui o escopo com variáveis que existem só para carregar um valor de uma linha para outra.

Com o pipe operator, o mesmo código fica assim:

$result = $input
    |> trim(...)
    |> strtoupper(...)
    |> str_split(...);

Leitura de cima para baixo, na ordem em que as operações acontecem. O (...) depois do nome da função é a sintaxe de first-class callable introduzida no PHP 8.1 -- ela transforma a função em um Closure que o pipe consegue chamar.

Existe uma limitação que vale conhecer: cada callable na cadeia precisa aceitar exatamente um argumento obrigatório. Se você precisa passar parâmetros extras, use uma arrow function:

$result = $input
    |> trim(...)
    |> strtoupper(...)
    |> (fn($s) => substr($s, 0, 10));

Repare nos parênteses ao redor da arrow function -- eles são obrigatórios quando ela aparece numa cadeia de pipe. Detalhe de sintaxe que o linter vai pegar, mas é bom saber antes.

Para quem já usou pipe em Elixir, F# ou shell Unix, a mecânica é a mesma. Para quem vem de JavaScript, é o equivalente do pipeline operator que o TC39 discute há anos sem chegar a consenso. O PHP saiu na frente nessa.

Clone with: cópias imutáveis sem boilerplate

O PHP 8.4 trouxe property hooks e asymmetric visibility. O 8.5 complementa com o clone with, que resolve o maior incômodo de quem trabalha com objetos readonly: criar cópias modificadas.

Antes do 8.5, se você tinha uma classe readonly e queria uma cópia com um campo alterado, precisava de um método wither manual:

final readonly class Money
{
    public function __construct(
        public int $amount,
        public string $currency,
    ) {}

    public function withAmount(int $amount): self
    {
        return new self($amount, $this->currency);
    }
}

Para cada propriedade que você quisesse alterar, um método with* novo. Numa classe com dez propriedades, são dez métodos que fazem quase a mesma coisa.

O clone with resolve isso de uma vez:

$price = new Money(1000, 'BRL');
$discounted = clone($price, ['amount' => 800]);

O clone agora aceita um segundo argumento: um array associativo onde as chaves são nomes de propriedades e os valores são os novos dados. A cópia mantém todas as propriedades originais exceto as que você sobrescreveu.

O que acontece por baixo: o PHP cria o clone primeiro (com todos os valores originais), depois aplica as atribuições na ordem em que aparecem no array. Type checks, visibility checks e property hooks continuam funcionando. A única restrição que o clone with relaxa é a de escrita única das propriedades readonly -- e isso é o ponto inteiro do recurso.

Isso encaixa bem no padrão de value objects imutáveis que frameworks como Laravel e Symfony usam em responses, DTOs e configurações. Onde antes você escrevia $response->withStatus(404) com um método manual, agora pode usar clone($response, ['statusCode' => 404]) sem precisar que a classe implemente nada.

Funções de array que faltavam

O PHP 7.3 adicionou array_key_first() e array_key_last() para pegar a primeira e última chave de um array. Mas pegar o primeiro e último valor exigia reset() (que move o ponteiro interno) ou current(array_slice($arr, 0, 1)) (que cria um array novo).

O PHP 8.5 finalmente adicionou array_first() e array_last():

$users = ['Alice', 'Bob', 'Charlie'];

$first = array_first($users); // 'Alice'
$last = array_last($users);   // 'Charlie'

Funcionam com arrays associativos também. Retornam null para arrays vazios. Não mexem no ponteiro interno.

Parece pouca coisa, mas pense em quantas vezes você escreveu $items[0] assumindo um array indexado e quebrou quando recebeu um array associativo, ou usou reset() sem perceber que estava alterando estado.

Vale lembrar que o PHP 8.4 já tinha adicionado array_find(), array_find_key(), array_any() e array_all(). Junto com array_first() e array_last(), a manipulação de arrays finalmente cobre os casos comuns sem precisar de workarounds.

A nova extensão Uri

O PHP sempre teve parse_url(), mas quem já usou sabe das limitações. Ela não valida o formato, não normaliza componentes e não segue nenhuma spec de verdade. A extensão Uri do PHP 8.5 substitui isso com duas implementações que seguem padrões reais.

A primeira é Uri\Rfc3986\Uri, que segue a RFC 3986 (a spec genérica de URIs). A segunda é Uri\WhatWg\Url, que segue o padrão WHATWG -- o mesmo que os browsers usam. A escolha depende do contexto: para APIs e backend, RFC 3986 é o caminho. Para parsing de URLs que vêm de input de usuário no browser, WHATWG é mais permissiva e espelha o comportamento real dos navegadores.

Um exemplo com RFC 3986:

use Uri\Rfc3986\Uri;

$url = new Uri('HTTPS://example.com:443/api/users?page=1');

echo $url->getScheme();   // 'https' (normalizado para lowercase)
echo $url->getHost();     // 'example.com'
echo $url->getPort();     // 443
echo $url->getPath();     // '/api/users'
echo $url->getQuery();    // 'page=1'

Os objetos são imutáveis. Para modificar, use os métodos with*:

$apiUrl = $url
    ->withPath('/api/products')
    ->withQuery('category=electronics&page=2')
    ->withPort(null);

echo $apiUrl->toString();
// https://example.com/api/products?category=electronics&page=2

A extensão vem embutida no PHP 8.5 -- não precisa instalar nada. Se você tem código que usa parse_url() com validação manual por cima, a migração para Uri\Rfc3986\Uri elimina essa camada inteira.

Closures em expressões constantes

Essa é a feature que parece menor mas abre possibilidades novas. Antes do 8.5, expressões constantes (valores default de parâmetros, propriedades, constantes de classe e atributos) só aceitavam valores escalares. Closures estavam fora.

Agora você pode usar closures e first-class callables em qualquer expressão constante:

function process(
    array $items,
    Closure $transform = strtoupper(...)
): array {
    return array_map($transform, $items);
}

$result = process(['hello', 'world']);
// ['HELLO', 'WORLD']

O strtoupper(...) é um first-class callable resolvido em tempo de compilação. Isso funciona em default de parâmetros, em propriedades de classe, em constantes e em atributos. A restrição: apenas closures estáticas e first-class callables puros são aceitos -- nada de $this, nada de use() capturando variáveis externas, nada de arrow functions. Expressões constantes não podem depender de valores de runtime.

Onde isso muda as coisas: pipelines de validação, estratégias de transformação, callbacks default. Antes, o padrão era aceitar null e checar dentro da função. Agora, o valor default já é a implementação concreta.

Deprecações que preparam o terreno para o PHP 9

As deprecações do 8.5 sinalizam o que vai quebrar no PHP 9.0. Nenhuma delas remove funcionalidade agora -- todas ainda funcionam mas emitem warnings.

As mais relevantes:

  • __sleep() e __wakeup() foram soft-deprecated. A recomendação é usar __serialize() e __unserialize(), que existem desde o PHP 7.4 e oferecem controle maior sobre o formato serializado
  • O operador backtick (` `) como alias de shell_exec() foi deprecated. Se você tem código com ls -la , hora de trocar por shell_exec('ls -la')`
  • Casts não-canônicos como (boolean), (integer), (double) e (binary) foram deprecated em favor de (bool), (int), (float) e (string)
  • As constantes MHASH_* foram aposentadas. Use as constantes de hash_* diretamente

Se você roda ferramentas de análise estática como PHPStan ou Psalm, esses warnings já aparecem antes de chegar em produção. Se não roda, esse é um bom momento para começar.

O PHP 8.5 recebe bug fixes até novembro de 2027 e patches de segurança até novembro de 2029. Tempo de sobra para migrar sem pressa, mas não para ignorar.

Conclusão

Das features novas, o pipe operator é a que mais muda o código no dia a dia. Encadear transformações de dados é uma operação que aparece em todo tipo de aplicação, e fazer isso sem variáveis intermediárias nem aninhamento deixa o código mais legível sem custo de performance relevante na prática.

O clone with resolve um problema real de quem trabalha com value objects, e as funções array_first()/array_last() eliminam gambiarras que todo dev PHP conhece. A extensão Uri é menos visível no dia a dia, mas quem lida com parsing de URLs vai sentir a diferença.

Se eu fosse escolher uma coisa para adotar primeiro: pipe operator. Ele muda a forma de pensar composição de funções em PHP, e uma vez que você se acostuma, não volta.

Referências pesquisadas nesta publicação