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.
- 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ão | Lançamento | Destaques |
|---|---|---|
| 8.0 | nov/2020 | match, argumentos nomeados, ?->, promoção no construtor, union types, atributos, JIT |
| 8.1 | nov/2021 | Enums, readonly, fibers, strlen(...), never |
| 8.2 | dez/2022 | Classes readonly, DNF, true/false/null como tipos, propriedades dinâmicas obsoletas |
| 8.3 | nov/2023 | #[\Override], constantes de classe tipadas, json_validate |
| 8.4 | nov/2024 | Property hooks, visibilidade assimétrica, array_find, new sem parênteses, #[\Deprecated] |
| 8.5 | nov/2025 | Pipe |>, 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+
| Recurso | Exemplo | Aula |
|---|---|---|
match | match ($status) { 200 => "OK", 404 => "Não encontrado", default => "Outro" } | match |
| Argumentos nomeados | str_pad(string: "7", length: 3, pad_string: "0", pad_type: STR_PAD_LEFT) | Parâmetros |
Operador nullsafe ?-> | $cliente?->endereco?->cidade | ?? e ?-> |
| Promoção no construtor | public function __construct(public string $nome) {} | Construtores |
Union types, mixed, static | function dobro(int|float $n): int|float | Tipos Avançados |
| Atributos | #[Rota('/x')] | Atributos |
str_contains, str_starts_with, str_ends_with | str_contains("Caneca de café", "café") | Funções de String |
throw como expressão | $nome = $_GET['nome'] ?? throw new InvalidArgumentException(…); | Exceções |
| JIT | Compilaçã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+
| Recurso | Exemplo | Aula |
|---|---|---|
| Enums | enum Status: string { case Pago = 'pago'; } | Enums |
Propriedades readonly | public readonly int $id | readonly e Property Hooks |
| Fibers | new 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&B | function parar(): never | Tipos Avançados |
array_is_list | array_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+
| Recurso | Exemplo | Aula |
|---|---|---|
Classes readonly | readonly class Ponto { … } (todas as propriedades ficam readonly) | readonly e Property Hooks |
| DNF types | (Countable&ArrayAccess)|null | Tipos Avançados |
true, false, null como tipos | function salvar(): true | Tipos Avançados |
Extensão Random | new Random\Randomizer(…) | — |
#[\SensitiveParameter] | Esconde senhas nos rastros de erro | Atributos |
| 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+
| Recurso | Exemplo | Aula |
|---|---|---|
#[\Override] | Erro se o método não substitui nenhum | Atributos |
| Constantes de classe tipadas | public const string MOEDA = "BRL"; | static |
json_validate | json_validate('{"nome": "Ana"}') | JSON |
| Constante de classe dinâmica | Loja::{$qual} | — |
mb_str_pad | mb_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+
| Recurso | Exemplo | Aula |
|---|---|---|
| Property hooks | public string $nome { set(string $v) => trim($v); } | readonly e Property Hooks |
| Visibilidade assimétrica | public private(set) float $preco: lê de fora, só muda de dentro | Visibilidade |
array_find, array_find_key, array_any, array_all | array_find($precos, fn($v) => $v > 50) | Funções de Array |
new sem parênteses em volta | new DateTime('2026-10-16')->format('d/m/Y') | Classes e Objetos |
#[\Deprecated] | Aviso ao usar uma função sua marcada | Atributos |
mb_trim, mb_ucfirst | mb_trim(" café ") | mbstring |
T $x = null implícito obsoleto | Use ?T $x = null | Tipos 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+
| Recurso | Exemplo | Aula |
|---|---|---|
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 ignorado | Atributos |
array_first, array_last | array_first($frutas) | Funções de Array |
Extensão uri | Uri\Rfc3986\Uri::parse(…), new Uri\WhatWg\Url(…) | (abaixo) |
| Rastro (stack trace) também nos erros fatais de compilação | As linhas Stack trace: #0 {main} que aparecem em todo o guia | Lendo 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 propriedadesreadonly, oclonecom mudanças precisa ser feito dentro da classe (como no métodocomY), porquereadonlysó 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
| Antigo | Situação | Use |
|---|---|---|
each(), create_function(), __autoload() | Removidos no 8.0 | foreach, funções anônimas, spl_autoload_register (Autoload) |
| Propriedade dinâmica | Obsoleta no 8.2 | Declarar a propriedade |
"${var}" | Obsoleto no 8.2 | "{$var}" ou "$var" (veja Interpolação) |
utf8_encode/utf8_decode | Obsoletos no 8.2 | mb_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.5 | shell_exec('comando') |
null como chave de array | Obsoleto 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
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.
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.
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?
2. O conserto do aviso
O aviso diz Non-canonical cast (integer) is deprecated. Complete o conserto:
3. Último valor
Complete com a função do PHP 8.5 que devolve o último valor de um array:
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ãouri. - Obsoleto = funciona com
Deprecated; removido = erro. Leia os avisos e troque antes da próxima versão.