PHP Novidades do 8.0 ao 8.5

O PHP lança uma versão nova por ano, sempre em novembro. Do 8.0 (2020) ao 8.5 (2025), a linguagem mudou muito: ganhou match, enums, propriedades somente-leitura, o operador pipe… Esta página é um mapa: o que cada versão trouxe, um exemplo curto de cada recurso rodado de verdade no PHP 8.5 do laboratório, a aula do guia onde ele é explicado, e o que foi removido ou ficou obsoleto pelo caminho.

Você vai aprender
  • Os recursos mais importantes de cada versão, do 8.0 ao 8.5.
  • Como reconhecer, num código, a versão mínima de PHP que ele exige.
  • O que foi removido (e dá erro) e o que está obsoleto (e dá aviso Deprecated).
  • A modernizar um trecho de código antigo.

Não precisa decorar nada daqui. Use como consulta quando encontrar um código ou tutorial e quiser saber "isso funciona no meu PHP?". Para ver a versão instalada: php -v (veja Versões do PHP).

$ php -v
PHP 8.5.8 (cli) (built: Jul  7 2026 05:21:52) (NTS gcc 14.2.0)
Copyright (c) The PHP Group
Built by static-php-cli 2.8.6
Zend Engine v4.5.8, Copyright (c) Zend Technologies
    with Zend OPcache v8.5.8, Copyright (c), by Zend Technologies

A linha do tempo

VersãoLançamentoDestaques
8.0nov/2020match, argumentos nomeados, ?->, promoção no construtor, union types, atributos, JIT
8.1nov/2021Enums, readonly, fibers, strlen(...), never
8.2dez/2022Classes readonly, DNF, true/false/null como tipos, propriedades dinâmicas obsoletas
8.3nov/2023#[\Override], constantes de classe tipadas, json_validate
8.4nov/2024Property hooks, visibilidade assimétrica, array_find, new sem parênteses, #[\Deprecated]
8.5nov/2025Pipe |>, clone com propriedades, #[\NoDiscard], array_first/array_last, extensão uri

Cada versão recebe correções de erros por 2 anos e de segurança por mais 2. Em outubro de 2026, as versões com suporte são da 8.2 em diante; o 8.0 e o 8.1 já não recebem nem correções de segurança.


PHP 8.0 PHP 8.0+

RecursoExemploAula
matchmatch ($status) { 200 => "OK", 404 => "Não encontrado", default => "Outro" }match
Argumentos nomeadosstr_pad(string: "7", length: 3, pad_string: "0", pad_type: STR_PAD_LEFT)Parâmetros
Operador nullsafe ?->$cliente?->endereco?->cidade?? e ?->
Promoção no construtorpublic function __construct(public string $nome) {}Construtores
Union types, mixed, staticfunction dobro(int|float $n): int|floatTipos Avançados
Atributos#[Rota('/x')]Atributos
str_contains, str_starts_with, str_ends_withstr_contains("Caneca de café", "café")Funções de String
throw como expressão$nome = $_GET['nome'] ?? throw new InvalidArgumentException(…);Exceções
JITCompilação para código de máquina (ganho em contas pesadas; pouco efeito em sites)—
<?php
// match
$status = 404;
echo match ($status) { 200 => "OK", 404 => "Não encontrado", default => "Outro" }, "\n";

// named arguments
echo str_pad(string: "7", length: 3, pad_string: "0", pad_type: STR_PAD_LEFT), "\n";

// nullsafe
$cliente = null;
var_dump($cliente?->endereco?->cidade);

// constructor promotion
class Produto
{
    public function __construct(public string $nome, public float $preco) {}
}
$p = new Produto("Caneca", 19.9);
echo "$p->nome: $p->preco\n";

// union types
function dobro(int|float $n): int|float { return $n * 2; }
var_dump(dobro(2.5));

// atributos
#[Attribute]
class Rota { public function __construct(public string $c) {} }
#[Rota('/x')]
function f() {}
echo (new ReflectionFunction('f'))->getAttributes()[0]->getName(), "\n";

// str_contains / throw como expressão
var_dump(str_contains("Caneca de café", "café"));
$nome = $_GET['nome'] ?? throw new InvalidArgumentException("nome obrigatório");
$ php v80.php
Não encontrado
007
NULL               « $cliente é null: ?-> parou ali, sem erro
Caneca: 19.9
float(5)
Rota
bool(true)

Fatal error: Uncaught InvalidArgumentException: nome obrigatório in /home/ana/nov/v80.php:34
Stack trace:
#0 {main}
  thrown in /home/ana/nov/v80.php on line 34

A última linha deveria dar erro: no terminal não existe $_GET['nome'], e o ?? throw lançou a exceção, como esperado.


PHP 8.1 PHP 8.1+

RecursoExemploAula
Enumsenum Status: string { case Pago = 'pago'; }Enums
Propriedades readonlypublic readonly int $idreadonly e Property Hooks
Fibersnew Fiber(function () { Fiber::suspend(…); })(base de bibliotecas assíncronas; você raramente usa direto)
Callable de primeira classe$tamanho = strlen(...);Callables
Tipo never, interseção A&Bfunction parar(): neverTipos Avançados
array_is_listarray_is_list([1, 2, 3])Funções de Array
<?php
enum Status: string
{
    case Pago = 'pago';
    case Pendente = 'pendente';
}
echo Status::from('pago')->name, "\n";

class Pedido
{
    public function __construct(public readonly int $id) {}
}
$p = new Pedido(7);
try {
    $p->id = 8;
} catch (Error $e) {
    echo get_class($e), ": ", $e->getMessage(), "\n";
}

$fiber = new Fiber(function (): string {
    $x = Fiber::suspend("pausei");
    return "recebi $x";
});
echo $fiber->start(), "\n";
$fiber->resume("oi");
echo $fiber->getReturn(), "\n";

$tamanho = strlen(...);
echo $tamanho("caneca"), "\n";

function parar(): never { throw new RuntimeException("fim"); }

var_dump(array_is_list([1, 2, 3]), array_is_list([1 => 'a']));
$ php v81.php
Pago
Error: Cannot modify readonly property Pedido::$id
pausei             « a fiber pausou e devolveu um valor
recebi oi          « foi retomada com "oi" e terminou
6
bool(true)
bool(false)        « chaves que não começam em 0: não é lista

Uma fiber ("fibra") é uma função que pode pausar no meio (Fiber::suspend) e ser retomada depois (resume), do ponto onde parou. Bibliotecas como ReactPHP e Amp usam fibers para fazer várias tarefas de rede "ao mesmo tempo". No dia a dia de um site, você não vai criá-las.


PHP 8.2 PHP 8.2+

RecursoExemploAula
Classes readonlyreadonly class Ponto { … } (todas as propriedades ficam readonly)readonly e Property Hooks
DNF types(Countable&ArrayAccess)|nullTipos Avançados
true, false, null como tiposfunction salvar(): trueTipos Avançados
Extensão Randomnew Random\Randomizer(…)—
#[\SensitiveParameter]Esconde senhas nos rastros de erroAtributos
Propriedades dinâmicas obsoletas$c->idade = 30; sem declarar $idade(abaixo)
<?php
readonly class Ponto
{
    public function __construct(public int $x, public int $y) {}
}
$p = new Ponto(1, 2);
try { $p->x = 5; } catch (Error $e) { echo $e->getMessage(), "\n"; }

function f((Countable&ArrayAccess)|null $x): string { return $x === null ? "nulo" : "objeto"; }
echo f(null), " / ", f(new ArrayObject([])), "\n";

function salvar(): true { return true; }
var_dump(salvar());

class Cliente { public string $nome = "Ana"; }
$c = new Cliente();
$c->idade = 30;
echo "idade: $c->idade\n";

$r = new Random\Randomizer(new Random\Engine\Mt19937(42));
echo $r->getInt(1, 100), "\n";
$ php v82.php
Cannot modify readonly property Ponto::$x
nulo / objeto
bool(true)

Deprecated: Creation of dynamic property Cliente::$idade is deprecated in /home/ana/nov/v82.php on line 17
idade: 30
43                 « sorteio com semente fixa (42): sempre o mesmo número

PHP 8.3 PHP 8.3+

RecursoExemploAula
#[\Override]Erro se o método não substitui nenhumAtributos
Constantes de classe tipadaspublic const string MOEDA = "BRL";static
json_validatejson_validate('{"nome": "Ana"}')JSON
Constante de classe dinâmicaLoja::{$qual}—
mb_str_padmb_str_pad("Café", 8, ".")mbstring
<?php
class Pagamento { public function processar(): string { return "pag"; } }
class Pix extends Pagamento
{
    #[\Override]
    public function processar(): string { return "pix"; }
}
echo (new Pix())->processar(), "\n";

class Loja
{
    public const string MOEDA = "BRL";
    public const float TAXA = 0.05;
}
echo Loja::MOEDA, "\n";

var_dump(json_validate('{"nome": "Ana"}'), json_validate('{nome: Ana}'));

$qual = "MOEDA";
echo Loja::{$qual}, "\n";

echo mb_str_pad("Café", 8, "."), "|\n";
$ php v83.php
pix
BRL
bool(true)
bool(false)        « sem aspas nas chaves não é JSON válido
BRL
Café....|          « 8 letras (str_pad contaria o é como 2)

Com o tipo na constante, o PHP confere o valor:

$ php v83erro.php       « public const int TAXA = 0.05;

Fatal error: Cannot use float as value for class constant Loja::TAXA of type int in /home/ana/nov/v83erro.php on line 4
Stack trace:
#0 {main}

PHP 8.4 PHP 8.4+

RecursoExemploAula
Property hookspublic string $nome { set(string $v) => trim($v); }readonly e Property Hooks
Visibilidade assimétricapublic private(set) float $preco: lê de fora, só muda de dentroVisibilidade
array_find, array_find_key, array_any, array_allarray_find($precos, fn($v) => $v > 50)Funções de Array
new sem parênteses em voltanew DateTime('2026-10-16')->format('d/m/Y')Classes e Objetos
#[\Deprecated]Aviso ao usar uma função sua marcadaAtributos
mb_trim, mb_ucfirstmb_trim(" café ")mbstring
T $x = null implícito obsoletoUse ?T $x = nullTipos Avançados
<?php
class Produto
{
    public string $nome {
        set(string $valor) => mb_convert_case(trim($valor), MB_CASE_TITLE);
    }

    public private(set) float $preco = 0;

    public function __construct(string $nome, float $preco)
    {
        $this->nome = $nome;
        $this->preco = $preco;
    }
}

$p = new Produto("  caneca de café ", 19.9);
echo $p->nome, " - ", $p->preco, "\n";
try {
    $p->preco = 1;
} catch (Error $e) {
    echo $e->getMessage(), "\n";
}

$precos = [10, 55, 30, 80];
var_dump(array_find($precos, fn($v) => $v > 50));
var_dump(array_find_key($precos, fn($v) => $v > 50));
var_dump(array_any($precos, fn($v) => $v > 70), array_all($precos, fn($v) => $v > 5));

echo new DateTime('2026-10-16')->format('d/m/Y'), "\n";

#[\Deprecated("use novo()")]
function velho() {}
velho();

var_dump(mb_trim("  café  "));
$ php v84.php
Caneca De Café - 19.9             « o hook "set" arrumou o nome ao gravar
Cannot modify private(set) property Produto::$preco from global scope
int(55)            « o primeiro valor maior que 50
int(1)             « a chave dele
bool(true)         « algum é maior que 70?
bool(true)         « todos são maiores que 5?
16/10/2026

Deprecated: Function velho() is deprecated, use novo() in /home/ana/nov/v84.php on line 34
string(5) "café"

Antes do 8.4, a linha do DateTime precisava de parênteses extras: (new DateTime('2026-10-16'))->format('d/m/Y'). As duas formas continuam valendo.

Missão: use array_find (8.4) para achar o primeiro produto com preço acima de 40 e array_last (8.5) para pegar o último da lista. Substitua os dois ???. A saída deve ser Primeiro caro: Camiseta e Último da lista: Boné.

<?php
$produtos = [
    ['nome' => 'Caneca', 'preco' => 19.9],
    ['nome' => 'Camiseta', 'preco' => 49.9],
    ['nome' => 'Moletom', 'preco' => 129.9],
    ['nome' => 'Boné', 'preco' => 35.0],
];

$caro = null;   // ??? use array_find
$ultimo = $produtos[0];   // ??? use array_last

echo "Primeiro caro: ", $caro['nome'] ?? 'nenhum', "\n";
echo "Último da lista: ", $ultimo['nome'], "\n";
<?php
$produtos = [
    ['nome' => 'Caneca', 'preco' => 19.9],
    ['nome' => 'Camiseta', 'preco' => 49.9],
    ['nome' => 'Moletom', 'preco' => 129.9],
    ['nome' => 'Boné', 'preco' => 35.0],
];

$caro = array_find($produtos, fn($p) => $p['preco'] > 40);
$ultimo = array_last($produtos);

echo "Primeiro caro: ", $caro['nome'] ?? 'nenhum', "\n";
echo "Último da lista: ", $ultimo['nome'], "\n";

PHP 8.5 PHP 8.5+

RecursoExemploAula
Operador pipe |>" Olá Mundo " |> trim(...) |> mb_strtoupper(...)Operador Pipe
clone com propriedades ("clone with")clone($this, ['y' => $y])(abaixo)
#[\NoDiscard] e (void)Aviso se o retorno for ignoradoAtributos
array_first, array_lastarray_first($frutas)Funções de Array
Extensão uriUri\Rfc3986\Uri::parse(…), new Uri\WhatWg\Url(…)(abaixo)
Rastro (stack trace) também nos erros fatais de compilaçãoAs linhas Stack trace: #0 {main} que aparecem em todo o guiaLendo Erros
<?php
echo "  Olá Mundo  " |> trim(...) |> mb_strtoupper(...), "\n";

final readonly class Ponto
{
    public function __construct(public int $x, public int $y) {}

    public function comY(int $y): static
    {
        return clone($this, ['y' => $y]);
    }
}
$a = new Ponto(1, 2);
$b = $a->comY(5);
echo "a=($a->x,$a->y) b=($b->x,$b->y)\n";

class Etiqueta { public string $texto = ""; public string $cor = "azul"; }
$e1 = new Etiqueta();
$e2 = clone($e1, ['texto' => 'Promoção', 'cor' => 'vermelho']);
echo "$e1->cor / $e2->texto $e2->cor\n";

#[\NoDiscard]
function contar(array $l): int { return count($l); }
contar([1, 2]);
(void) contar([1, 2]);

$frutas = ['b' => 'banana', 'a' => 'abacate', 'c' => 'caju'];
var_dump(array_first($frutas), array_last($frutas), array_first([]));

$uri = Uri\Rfc3986\Uri::parse("https://loja.exemplo.com:8080/produtos?id=7#fotos");
echo $uri->getScheme(), " | ", $uri->getHost(), " | ", $uri->getPort(), " | ", $uri->getPath(), " | ", $uri->getQuery(), " | ", $uri->getFragment(), "\n";
$url = new Uri\WhatWg\Url("HTTPS://Loja.Exemplo.com/a/../produtos");
echo $url->toAsciiString(), "\n";
var_dump(Uri\Rfc3986\Uri::parse("http://exemplo com/"));
echo PHP_VERSION, "\n";
$ php v85.php
OLÁ MUNDO
a=(1,2) b=(1,5)                   « $a não mudou; $b é uma cópia com y=5
azul / Promoção vermelho

Warning: The return value of function contar() should either be used or intentionally ignored by casting it as (void) in /home/ana/nov/v85.php on line 24
string(6) "banana"                   « o primeiro, seja qual for a chave
string(4) "caju"
NULL                              « array vazio: null
https | loja.exemplo.com | 8080 | /produtos | id=7 | fotos
https://loja.exemplo.com/produtos « a WHATWG normalizou maiúsculas e o "/a/.."
NULL                              « endereço inválido (tem espaço): parse devolve null
8.5.8
clone($objeto, [...])
Copia o objeto já trocando algumas propriedades. É o jeito de "mudar" objetos readonly: você não muda, cria uma cópia alterada. Com propriedades readonly, o clone com mudanças precisa ser feito dentro da classe (como no método comY), porque readonly só permite gravar de dentro (veja o erro abaixo).
array_first / array_last
O primeiro e o último valor do array, sem mexer no ponteiro interno (diferente de reset/end) e sem depender de as chaves serem 0, 1, 2… Array vazio dá null.
Uri\Rfc3986\Uri
Analisa endereços seguindo a norma RFC 3986, com um método para cada parte. Substitui com vantagens o antigo parse_url(), que aceita muita coisa errada.
Uri\WhatWg\Url
Analisa endereços do jeito que os navegadores fazem (norma WHATWG), normalizando-os.

Tentando usar o clone com mudanças de fora de uma classe readonly:

$ php v85.php        « versão com $b = clone($a, ['y' => 5]); fora da classe

Fatal error: Uncaught Error: Cannot modify protected(set) readonly property Ponto::$y from global scope in /home/ana/nov/v85.php:9
Stack trace:
#0 /home/ana/nov/v85.php(9): clone(Object(Ponto), Array)
#1 {main}
  thrown in /home/ana/nov/v85.php on line 9

"Não é possível modificar a propriedade readonly Ponto::$y, que é protected(set), a partir do escopo global". Propriedades readonly só podem ser gravadas de dentro da classe; o clone com mudanças obedece à mesma regra.


O que saiu: removido e obsoleto

Duas palavras importantes:

  • Obsoleto (deprecated): ainda funciona, mas mostra um aviso Deprecated: … e vai ser removido numa versão futura. É o momento de trocar.
  • Removido: não existe mais; dá erro.

Propriedades dinâmicas (obsoletas desde o 8.2)

Antes, dava para criar uma propriedade em qualquer objeto só atribuindo: $c->idade = 30;, mesmo sem $idade declarada na classe. Isso escondia erros de digitação ($c->nmoe = "Ana" criava uma propriedade nova em vez de reclamar). Desde o 8.2, avisa, como você viu acima:

Deprecated: Creation of dynamic property Cliente::$idade is deprecated in /home/ana/nov/v82.php on line 17

Conserto: declare a propriedade na classe (public int $idade;). Se a classe realmente precisa disso, marque-a com #[\AllowDynamicProperties]. Objetos stdClass (os de json_decode, por exemplo) continuam aceitando sem aviso:

<?php
#[\AllowDynamicProperties]
class Ficha {}
$f = new Ficha();
$f->qualquer = 1;
echo "sem aviso: $f->qualquer\n";
$o = new stdClass();
$o->nome = "Ana";
echo "stdClass: $o->nome\n";
$ php dinamica.php
sem aviso: 1
stdClass: Ana

Outros avisos que você pode encontrar no PHP 8.5

<?php
$nome = "Ana";
echo "Olá, ${nome}!\n";
echo utf8_encode("x"), "\n";
$n = (integer) "5";
$b = (boolean) 1;
$lista = ["a" => 1];
var_dump($lista[null] ?? "sem");
echo "fim\n";
$ php dep.php

Deprecated: Non-canonical cast (integer) is deprecated, use the (int) cast instead in /home/ana/nov/dep.php on line 5

Deprecated: Non-canonical cast (boolean) is deprecated, use the (bool) cast instead in /home/ana/nov/dep.php on line 6

Deprecated: Using ${var} in strings is deprecated, use {$var} instead in /home/ana/nov/dep.php on line 3
Olá, Ana!

Deprecated: Function utf8_encode() is deprecated since 8.2, visit the php.net documentation for various alternatives in /home/ana/nov/dep.php on line 4
x

Deprecated: Using null as an array offset is deprecated, use an empty string instead in /home/ana/nov/dep.php on line 8
string(3) "sem"
fim

Repare que os avisos das linhas 3, 5 e 6 aparecem antes do Olá, Ana!: eles são dados quando o arquivo é compilado. Os das linhas 4 e 8, na hora em que a linha roda. E o programa chegou ao fim: obsoleto ainda funciona.

$ php crase.php      « $saida = `echo oi`;

Deprecated: The backtick (`) operator is deprecated, use shell_exec() instead in /home/ana/nov/crase.php on line 2
oi

Tabela: o que trocar

AntigoSituaçãoUse
each(), create_function(), __autoload()Removidos no 8.0foreach, funções anônimas, spl_autoload_register (Autoload)
Propriedade dinâmicaObsoleta no 8.2Declarar a propriedade
"${var}"Obsoleto no 8.2"{$var}" ou "$var" (veja Interpolação)
utf8_encode/utf8_decodeObsoletos no 8.2mb_convert_encoding()
function f(T $x = null)Obsoleto no 8.4?T $x = null
(integer), (boolean), (double), (binary)Obsoletos no 8.5(int), (bool), (float), (string)
Crase `comando`Obsoleta no 8.5shell_exec('comando')
null como chave de arrayObsoleto no 8.5"" (texto vazio), se era isso que você queria

Tentando usar uma função removida:

$ php rem.php        « each($a);

Fatal error: Uncaught Error: Call to undefined function each() in /home/ana/nov/rem.php:3
Stack trace:
#0 {main}
  thrown in /home/ana/nov/rem.php on line 3

Quando dá errado

Parse error num código copiado de um tutorial

O que significa: o código usa um recurso mais novo que o seu PHP (por exemplo, |> num PHP 8.4, ou enum num PHP 8.0).

Como consertar: php -v, procure o recurso nesta página e atualize o PHP, ou reescreva com a forma antiga.

Call to undefined function array_find() (ou json_validate, array_first…)

O que significa: a função é mais nova que o seu PHP: array_find é do 8.4; array_first, do 8.5; json_validate, do 8.3.

Como consertar: atualize o PHP. Se não der, escreva o equivalente com foreach, ou instale um pacote "polyfill" com o Composer (como o symfony/polyfill-php84), que cria essas funções em versões antigas.

Uma tela cheia de Deprecated depois de atualizar o PHP

O que significa: o código usa recursos marcados como obsoletos na versão nova. Ele ainda funciona.

Como consertar: leia cada aviso (ele quase sempre diz o que usar), troque usando a tabela acima e não esconda os avisos: na próxima versão, eles viram erros.


Exercícios

1. A versão mínima

Um código usa enum e readonly. Qual a versão mínima do PHP?

PHP

2. O conserto do aviso

O aviso diz Non-canonical cast (integer) is deprecated. Complete o conserto:

$n = $_GET['n'];

3. Último valor

Complete com a função do PHP 8.5 que devolve o último valor de um array:

$ultimo = ($lista);

4. Em qual versão o operador pipe |> apareceu?

5. No PHP 8.5, $cliente->idade = 30; numa classe que não declara $idade:

6. Qual a diferença entre "obsoleto" e "removido"?

Missão final: modernize esta classe escrita no estilo do PHP 7: (1) use a promoção no construtor com tipos (private string $cliente, private string $status) e apague as propriedades e atribuições repetidas; (2) troque o switch por match; (3) declare o retorno : string. A saída não deve mudar.

<?php
class Pedido
{
    private $cliente;
    private $status;

    public function __construct($cliente, $status)
    {
        $this->cliente = $cliente;
        $this->status = $status;
    }

    public function descricao()
    {
        switch ($this->status) {
            case 'pago':
                $texto = 'Pago';
                break;
            case 'enviado':
                $texto = 'A caminho';
                break;
            default:
                $texto = 'Aguardando pagamento';
        }
        return $this->cliente . ': ' . $texto;
    }
}

foreach ([new Pedido('Bruno', 'pago'), new Pedido('Carla', 'novo')] as $p) {
    echo $p->descricao(), "\n";
}
<?php
class Pedido
{
    public function __construct(
        private string $cliente,
        private string $status,
    ) {}

    public function descricao(): string
    {
        $texto = match ($this->status) {
            'pago' => 'Pago',
            'enviado' => 'A caminho',
            default => 'Aguardando pagamento',
        };
        return "$this->cliente: $texto";
    }
}

foreach ([new Pedido('Bruno', 'pago'), new Pedido('Carla', 'novo')] as $p) {
    echo $p->descricao(), "\n";
}

Resumo

  • 8.0: match, argumentos nomeados, ?->, promoção no construtor, union types, atributos, str_contains.
  • 8.1: enums, readonly, fibers, strlen(...), never, array_is_list.
  • 8.2: classes readonly, DNF, true/false/null; propriedades dinâmicas obsoletas.
  • 8.3: #[\Override], constantes tipadas, json_validate.
  • 8.4: property hooks, private(set), array_find/any/all, new X()->m(), #[\Deprecated].
  • 8.5: |>, clone($obj, [...]), #[\NoDiscard], array_first/array_last, extensão uri.
  • Obsoleto = funciona com Deprecated; removido = erro. Leia os avisos e troque antes da próxima versão.