PHP

PHP 8.x Moderno Já leu

11 min de leitura

PHP 8.x Moderno
O PHP 8.x trouxe as mudanças mais significativas da linguagem em uma década. Enums nativos eliminam a necessidade de constantes mágicas e classes de constantes. Fibers introduzem concorrência cooperativa no núcleo da lin

O PHP 8.x trouxe as mudanças mais significativas da linguagem em uma década. Enums nativos eliminam a necessidade de constantes mágicas e classes de constantes. Fibers introduzem concorrência cooperativa no núcleo da linguagem. Readonly classes tornam objetos de valor imutáveis com uma única palavra-chave. Atributos nativos substituem comentários PHPDoc para metadados estruturados e verificáveis.

Esses recursos não são apenas conveniências sintáticas — eles mudam a forma como você modela domínios, escreve código concorrente e documenta comportamentos esperados. Frameworks modernos já os adotam intensamente: o Laravel usa Enums em cast de Eloquent, usa Atributos para rotas e injeção de dependência, e o PHP-DI usa Atributos para configurar containers.


Enums nativos — PHP 8.1

Antes do PHP 8.1, simular enumerações exigia classes com constantes, bibliotecas externas como myclabs/php-enum, ou simplesmente strings e inteiros soltos. Enums nativos resolvem isso com suporte do tipo system: um enum é um tipo, não apenas um valor.

PHP tem dois tipos de enum: Pure Enums (sem valor associado) e Backed Enums (associados a string ou int).

<?php
declare(strict_types=1);

// ── PURE ENUM — sem valor subjacente ─────────────────────────────────
enum Naipe
{
    case Copas;
    case Ouros;
    case Espadas;
    case Paus;
}

$carta = Naipe::Copas;
echo $carta->name . "\n"; // Copas

// Enums são tipos — type hints funcionam
function jogar(Naipe $naipe): string
{
    return "Jogou: " . $naipe->name;
}
echo jogar(Naipe::Espadas) . "\n"; // Jogou: Espadas
// jogar("Copas") → TypeError — string não é Naipe

// ── BACKED ENUM — com valor string ───────────────────────────────────
enum Status: string
{
    case Pendente   = 'pendente';
    case Aprovado   = 'aprovado';
    case Cancelado  = 'cancelado';
    case Entregue   = 'entregue';

    // Métodos em enums — lógica de domínio junto ao tipo
    public function label(): string
    {
        return match($this) {
            Status::Pendente  => '⏳ Pendente',
            Status::Aprovado  => '✅ Aprovado',
            Status::Cancelado => '❌ Cancelado',
            Status::Entregue  => '📦 Entregue',
        };
    }

    public function podeSerCancelado(): bool
    {
        return in_array($this, [Status::Pendente, Status::Aprovado]);
    }
}

// Backed Enums: from() lança ValueError, tryFrom() retorna null
$status = Status::from('aprovado');          // Status::Aprovado
$status = Status::tryFrom('invalido');       // null — não lança exceção
echo Status::Pendente->value . "\n";         // pendente
echo Status::Aprovado->label() . "\n";       // ✅ Aprovado

// Listar todos os casos
foreach (Status::cases() as $caso) {
    echo $caso->value . " → " . $caso->label() . "\n";
}

// ── ENUM IMPLEMENTANDO INTERFACE ──────────────────────────────────────
interface TemLabel
{
    public function label(): string;
}

enum Prioridade: int implements TemLabel
{
    case Baixa  = 1;
    case Media  = 2;
    case Alta   = 3;
    case Critica = 4;

    public function label(): string
    {
        return match($this) {
            Prioridade::Baixa   => '🟢 Baixa',
            Prioridade::Media   => '🟡 Média',
            Prioridade::Alta    => '🟠 Alta',
            Prioridade::Critica => '🔴 Crítica',
        };
    }

    // Constantes em enums
    const PADRAO = self::Media;
}

echo Prioridade::Alta->label() . "\n";      // 🟠 Alta
echo Prioridade::PADRAO->label() . "\n";    // 🟡 Média

Readonly classes — PHP 8.2

PHP 8.1 introduziu readonly em propriedades individuais. PHP 8.2 tornou possível marcar a classe inteira como readonly — todas as propriedades tipadas se tornam readonly automaticamente. É o modelo perfeito para Value Objects e DTOs.

<?php
declare(strict_types=1);

// PHP 8.1 — readonly em propriedades individuais
class PontoV1
{
    public function __construct(
        public readonly float $x,
        public readonly float $y,
    ) {}
}

// PHP 8.2 — readonly class — todas as propriedades são readonly automaticamente
readonly class Ponto
{
    public function __construct(
        public float $x,
        public float $y,
    ) {}

    // Readonly classes não podem ter propriedades não-readonly
    // mas podem ter métodos normais
    public function distanciaAte(Ponto $outro): float
    {
        return sqrt(($this->x - $outro->x) ** 2 + ($this->y - $outro->y) ** 2);
    }

    // Padrão wither — retorna nova instância com valor alterado (imutabilidade)
    public function comX(float $x): static
    {
        return new static($x, $this->y);
    }

    public function comY(float $y): static
    {
        return new static($this->x, $y);
    }
}

$a = new Ponto(0.0, 0.0);
$b = new Ponto(3.0, 4.0);

echo $a->distanciaAte($b) . "\n"; // 5
// $a->x = 1.0; → Error: Cannot modify readonly property

$c = $a->comX(1.0)->comY(2.0);   // nova instância — $a intacto
echo $c->x . ", " . $c->y . "\n"; // 1, 2

// Value Objects como Readonly Classes
readonly class Dinheiro
{
    public function __construct(
        public int    $centavos,
        public string $moeda,
    ) {}

    public function somar(Dinheiro $outro): static
    {
        if ($this->moeda !== $outro->moeda) {
            throw new \InvalidArgumentException("Moedas diferentes: {$this->moeda} e {$outro->moeda}");
        }
        return new static($this->centavos + $outro->centavos, $this->moeda);
    }

    public function formatado(): string
    {
        return number_format($this->centavos / 100, 2, ',', '.') . " {$this->moeda}";
    }
}

$preco    = new Dinheiro(1999, 'BRL');
$frete    = new Dinheiro(1500, 'BRL');
$total    = $preco->somar($frete);
echo $total->formatado() . "\n"; // 34,99 BRL

Atributos nativos — PHP 8.0+

Atributos (antes chamados de Annotations em outras linguagens) permitem anexar metadados estruturados a classes, métodos, propriedades e parâmetros. Ao contrário dos comentários PHPDoc, atributos são verificados pelo PHP, podem receber parâmetros tipados, e são lidos via Reflection API com type safety.

<?php
declare(strict_types=1);

// Definindo um atributo — é uma classe com #[Attribute]
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)]
class Rota
{
    public function __construct(
        public readonly string $caminho,
        public readonly string $metodo = 'GET',
    ) {}
}

#[Attribute(Attribute::TARGET_PROPERTY)]
class Validar
{
    public function __construct(
        public readonly string $tipo,        // 'required', 'email', 'min', etc.
        public readonly mixed  $parametro = null,
    ) {}
}

#[Attribute(Attribute::TARGET_METHOD)]
class Cache
{
    public function __construct(
        public readonly int    $ttlSegundos = 300,
        public readonly string $prefixo     = '',
    ) {}
}

// Usando os atributos
#[Rota('/api/usuarios')]
class UsuarioController
{
    #[Rota('/api/usuarios/{id}', 'GET')]
    #[Cache(ttlSegundos: 60, prefixo: 'usuario')]
    public function mostrar(int $id): array
    {
        return ['id' => $id];
    }

    #[Rota('/api/usuarios', 'POST')]
    public function criar(
        #[Validar('required')]
        #[Validar('email')]
        string $email,

        #[Validar('required')]
        #[Validar('min', 8)]
        string $senha,
    ): array {
        return ['criado' => true];
    }
}

// Lendo atributos via Reflection — como frameworks fazem
$reflection = new ReflectionClass(UsuarioController::class);

// Lê atributo da classe
$atributosClasse = $reflection->getAttributes(Rota::class);
foreach ($atributosClasse as $attr) {
    $rota = $attr->newInstance();
    echo "Classe: {$rota->caminho} [{$rota->metodo}]\n";
    // Classe: /api/usuarios [GET]
}

// Lê atributos dos métodos
foreach ($reflection->getMethods() as $metodo) {
    foreach ($metodo->getAttributes(Rota::class) as $attr) {
        $rota = $attr->newInstance();
        echo "Método {$metodo->getName()}: {$rota->caminho} [{$rota->metodo}]\n";
    }
    foreach ($metodo->getAttributes(Cache::class) as $attr) {
        $cache = $attr->newInstance();
        echo "Cache: TTL={$cache->ttlSegundos}s prefixo='{$cache->prefixo}'\n";
    }
}

Fibers — PHP 8.1

Fibers são corrotinas de baixo nível — blocos de código que podem ser pausados e retomados, cedendo o controle ao chamador. Não são threads (não rodam em paralelo), mas permitem programação assíncrona cooperativa: múltiplas tarefas progridem intercaladas sem bloquear o processo.

Fibers são a base sobre a qual bibliotecas como ReactPHP e Amp implementam seus loops de eventos no PHP moderno.

<?php
declare(strict_types=1);

// Fiber básico — suspender e retomar
$fiber = new Fiber(function (): void {
    echo "Fiber: início\n";

    // Suspende e passa um valor para quem chamou resume()
    $valor = Fiber::suspend('primeiro suspend');
    echo "Fiber: retomado com '{$valor}'\n";

    $valor2 = Fiber::suspend('segundo suspend');
    echo "Fiber: retomado novamente com '{$valor2}'\n";

    echo "Fiber: fim\n";
});

// start() executa até o primeiro Fiber::suspend()
$resultado = $fiber->start();
echo "Código principal recebeu: '{$resultado}'\n";

// resume() continua de onde parou, passando um valor para o Fiber
$resultado2 = $fiber->resume('ping');
echo "Código principal recebeu: '{$resultado2}'\n";

$fiber->resume('pong');

echo "Fiber terminado: " . ($fiber->isTerminated() ? "sim" : "não") . "\n";

// Output:
// Fiber: início
// Código principal recebeu: 'primeiro suspend'
// Fiber: retomado com 'ping'
// Código principal recebeu: 'segundo suspend'
// Fiber: retomado novamente com 'pong'
// Fiber: fim
// Fiber terminado: sim

// ── SCHEDULER SIMPLES — demonstrando concorrência cooperativa ──────────
class Scheduler
{
    /** @var Fiber[] */
    private array $fibers = [];

    public function adicionar(Fiber $fiber): void
    {
        $this->fibers[] = $fiber;
    }

    public function executar(): void
    {
        // Inicia todos os fibers
        foreach ($this->fibers as $fiber) {
            $fiber->start();
        }

        // Continua rodando até todos terminarem
        while (!empty($this->fibers)) {
            foreach ($this->fibers as $i => $fiber) {
                if ($fiber->isSuspended()) {
                    $fiber->resume();
                }
                if ($fiber->isTerminated()) {
                    unset($this->fibers[$i]);
                }
            }
        }
    }
}

$scheduler = new Scheduler();

$scheduler->adicionar(new Fiber(function (): void {
    for ($i = 1; $i <= 3; $i++) {
        echo "Tarefa A — passo {$i}\n";
        Fiber::suspend(); // cede o controle
    }
}));

$scheduler->adicionar(new Fiber(function (): void {
    for ($i = 1; $i <= 3; $i++) {
        echo "Tarefa B — passo {$i}\n";
        Fiber::suspend(); // cede o controle
    }
}));

$scheduler->executar();
// Tarefa A — passo 1
// Tarefa B — passo 1
// Tarefa A — passo 2
// Tarefa B — passo 2
// Tarefa A — passo 3
// Tarefa B — passo 3

Outros recursos PHP 8.x notáveis

<?php
declare(strict_types=1);

// ── NAMED ARGUMENTS — PHP 8.0 ─────────────────────────────────────────
// Argumentos nomeados — ordem não importa, leitura mais clara
$resultado = array_slice(array: [1,2,3,4,5], offset: 1, length: 3);
// [2, 3, 4]

// Especialmente útil com construtores complexos
$data = new \DateTimeImmutable(
    datetime: '2024-01-15',
    timezone: new \DateTimeZone('America/Sao_Paulo'),
);

// ── MATCH EXPRESSION — PHP 8.0 ────────────────────────────────────────
// match é uma expressão (tem retorno), comparação estrita, sem fallthrough
$codigo = 200;
$descricao = match($codigo) {
    200, 201  => 'Sucesso',
    400       => 'Requisição inválida',
    401, 403  => 'Não autorizado',
    404       => 'Não encontrado',
    500       => 'Erro interno',
    default   => 'Código desconhecido',
};

// ── NULLSAFE OPERATOR — PHP 8.0 ───────────────────────────────────────
// Encadeia chamadas sem verificar null em cada passo
$cidade = $usuario?->endereco?->cidade?->nome;
// Equivale a: $usuario && $usuario->endereco && ... — mas conciso

// ── FIRST CLASS CALLABLES — PHP 8.1 ──────────────────────────────────
// Cria Closure a partir de qualquer callable com sintaxe limpa
$fn = strlen(...);         // Closure de função nativa
echo $fn("olá") . "\n";  // 3

$dobrar = fn(int $n): int => $n * 2;
$numeros = [1, 2, 3, 4, 5];
$dobrados = array_map($dobrar, $numeros); // [2, 4, 6, 8, 10]

// ── INTERSECTION TYPES — PHP 8.1 ──────────────────────────────────────
// Já vimos no artigo anterior — objetos que satisfazem múltiplas interfaces

// ── NEVER RETURN TYPE — PHP 8.1 ───────────────────────────────────────
// Declara que a função NUNCA retorna — sempre lança exceção ou termina o processo
function falharSempre(string $mensagem): never
{
    throw new \RuntimeException($mensagem);
}

// ── DNF TYPES — PHP 8.2 ───────────────────────────────────────────────
// Disjunctive Normal Form — combinação de union e intersection types
interface Stringable { public function __toString(): string; }
interface Countable  { public function count(): int; }
interface JsonSerializable { public function jsonSerialize(): mixed; }

// Aceita (Stringable & Countable) OU JsonSerializable
function processar((Stringable&Countable)|JsonSerializable $dado): void
{
    // ...
}

Resumo do artigo

Recurso Versão O que resolve
Enums PHP 8.1 Tipos enumerados com type safety — elimina constantes mágicas
Backed Enums PHP 8.1 Enum com valor string/int — serialização e persistência
Readonly properties PHP 8.1 Imutabilidade de propriedades individuais
Readonly classes PHP 8.2 Imutabilidade de toda a classe — ideal para Value Objects
Atributos nativos PHP 8.0 Metadados estruturados verificáveis — substituem PHPDoc
Fibers PHP 8.1 Corrotinas cooperativas — base para programação assíncrona
Named arguments PHP 8.0 Argumentos por nome — legibilidade e flexibilidade
Match expression PHP 8.0 Switch tipado com retorno, sem fallthrough
Nullsafe operator PHP 8.0 Encadeamento seguro em valores nullable
Never return type PHP 8.1 Declara funções que nunca retornam
DNF Types PHP 8.2 Combinação de union e intersection types

Exercício da semana

  1. Modele um sistema de pedidos usando Enums: StatusPedido (backed string com métodos label(), podeTransicionarPara(StatusPedido $novo): bool), MetodoPagamento (backed string: Pix, Cartao, Boleto) e TipoPrioridade (backed int: 1-3). Garanta que transições inválidas sejam bloqueadas.

  2. Crie uma readonly class Endereco com campos logradouro, numero, complemento (nullable), cidade, estado, cep. Implemente métodos formatado(): string e comComplemento(string $c): static. Crie também readonly class Cliente que compõe um Endereco.

  3. Defina três atributos: #[ValidarEmail], #[ValidarMinimo(int $min)] e #[ValidarMaximo(int $max)]. Crie uma função validarObjeto(object $obj): array que usa Reflection para ler os atributos das propriedades e retorna os erros encontrados.

  4. Escreva um mini-scheduler de Fibers que executa três "tarefas" em paralelo cooperativo: download simulado (5 passos), processamento (3 passos) e log (4 passos). Cada passo imprime o nome da tarefa e suspende. O scheduler intercala a execução.

  5. Desafio: combine os recursos do artigo para criar um micro-framework de rotas: atributo #[Rota(string $path, string $metodo)], um Router que usa Reflection para descobrir controllers e métodos anotados, e mapeia requisições {metodo} {path} para o método correto. Use um Enum MetodoHttp para os verbos.


Referências

  • PHP Manual — Enumerations: https://www.php.net/manual/pt_BR/language.enumerations.php
  • PHP Manual — Readonly Properties (8.1): https://www.php.net/manual/pt_BR/language.oop5.properties.php#language.oop5.properties.readonly-properties
  • PHP Manual — Readonly Classes (8.2): https://www.php.net/manual/pt_BR/language.oop5.basic.php#language.oop5.basic.class.readonly
  • PHP Manual — Attributes: https://www.php.net/manual/pt_BR/language.attributes.php
  • PHP Manual — Fibers: https://www.php.net/manual/pt_BR/language.fibers.php
  • PHP 8.0 Release Notes: https://www.php.net/releases/8.0/pt_BR.php
  • PHP 8.1 Release Notes: https://www.php.net/releases/8.1/pt_BR.php
  • PHP 8.2 Release Notes: https://www.php.net/releases/8.2/pt_BR.php
Comentários

Mais em PHP

Artigo 45 — APIs com Laravel
Artigo 45 — APIs com Laravel

Construir uma API com Laravel puro é possível — mas o framework oferece ferra...

Artigo 46 — Segurança Avançada em PHP
Artigo 46 — Segurança Avançada em PHP

O artigo 37 cobriu os vetores mais comuns — XSS, CSRF e SQL Injection. Segura...

PostgreSQL com PHP
PostgreSQL com PHP

O MySQL é o banco de dados mais comum no ecossistema PHP, mas o PostgreSQL é...