As Enums (enumerações) foram introduzidas no PHP 8.1 como uma forma de representar um conjunto fixo de valores nomeados. Diferente de constantes, enums oferecem tipagem forte e segurança, além de permitir métodos e interfaces. Nesta aula, vamos explorar os dois tipos de enums: puras (sem valor associado) e com backing (com valor escalar). Também veremos como adicionar métodos, casos de uso reais e por que enums são melhores que constantes em muitos cenários.

Enums são ideais para modelar estados, opções, tipos ou categorias que possuem um número limitado de possibilidades. Com o PHP 8.1, você pode definir enums de forma simples e usá-las com type hints, match expressions e muito mais.

Enums puros e com backing

Uma enum pura (pure enum) não possui valor associado. Cada case é um objeto singleton. Já uma enum com backing (backed enum) associa um valor escalar (int ou string) a cada case, permitindo serialização e conversão.

Exemplo de enum pura:

enum Status {
    case Pendente;
    case Processando;
    case Concluido;
    case Cancelado;
}

Exemplo de enum com backing (int):

enum StatusInt: int {
    case Pendente = 0;
    case Processando = 1;
    case Concluido = 2;
    case Cancelado = 3;
}

Exemplo com string:

enum StatusString: string {
    case Pendente = 'pendente';
    case Processando = 'processando';
    case Concluido = 'concluido';
    case Cancelado = 'cancelado';
}

Enums com backing implementam automaticamente a interface BackedEnum e fornecem métodos from() e tryFrom() para criar instâncias a partir do valor escalar.

Métodos em enums

Enums podem ter métodos, assim como classes. Isso permite encapsular lógica relacionada a cada case. Os métodos podem ser chamados diretamente na enum ou em um case específico.

Exemplo:

enum Status: string {
    case Pendente = 'pendente';
    case Processando = 'processando';
    case Concluido = 'concluido';
    case Cancelado = 'cancelado';

    public function label(): string {
        return match($this) {
            self::Pendente => 'Aguardando';
            self::Processando => 'Em andamento';
            self::Concluido => 'Finalizado';
            self::Cancelado => 'Cancelado';
        };
    }

    public function isFinal(): bool {
        return $this === self::Concluido || $this === self::Cancelado;
    }
}

echo Status::Pendente->label(); // 'Aguardando'
echo Status::Concluido->isFinal() ? 'Sim' : 'Não'; // 'Sim'

Métodos estáticos também são permitidos. Além disso, enums podem implementar interfaces, permitindo polimorfismo.

Casos de uso

Enums são perfeitas para modelar estados de um pedido, tipos de usuário, dias da semana, opções de configuração, etc. Exemplo prático em um sistema de pedidos:

enum OrderStatus: string {
    case New = 'new';
    case Paid = 'paid';
    case Shipped = 'shipped';
    case Delivered = 'delivered';
    case Cancelled = 'cancelled';

    public function next(): ?OrderStatus {
        return match($this) {
            self::New => self::Paid,
            self::Paid => self::Shipped,
            self::Shipped => self::Delivered,
            default => null,
        };
    }
}

function processOrder(OrderStatus $status): void {
    if ($status === OrderStatus::New) {
        // processar
    }
}

Outro caso comum é em formulários com opções fixas, como seleção de gênero, categorias, etc. Enums garantem que apenas valores válidos sejam usados, evitando erros.

vs constantes

Antes do PHP 8.1, era comum usar constantes de classe ou constantes globais para representar valores fixos. Exemplo:

class OrderStatus {
    const NEW = 'new';
    const PAID = 'paid';
    const SHIPPED = 'shipped';
}

Problemas com constantes:

  • Não há verificação de tipo: qualquer string pode ser passada onde se espera um status.
  • Não há métodos associados: a lógica fica espalhada em funções ou classes auxiliares.
  • Não há enumeração: você pode esquecer de verificar todos os casos.

Enums resolvem esses problemas: type safety, métodos integrados, e o compilador pode ajudar com match exaustivo. Além disso, enums podem ser usados em type hints, como parâmetros de função, garantindo que apenas valores válidos sejam passados.

Exemplo com constantes (frágil):

function getLabel(string $status): string {
    // sem garantia de que $status é um valor válido
}

Com enum:

function getLabel(OrderStatus $status): string {
    return $status->label();
}

Portanto, enums são superiores em legibilidade, segurança e manutenibilidade.

Boas práticas

  • Prefira enums com backing quando precisar serializar (banco de dados, API). Use string ou int conforme o contexto.
  • Evite enums muito grandes: se tiver muitos cases, considere se são realmente fixos.
  • Use match expressions com enums para garantir exaustividade (o PHP emitirá erro se algum case não for tratado).
  • Implemente interfaces em enums para aproveitar polimorfismo.

Referências

Exercícios

  1. Crie uma enum pura chamada DiaSemana com os dias da semana (segunda a domingo). Depois, crie uma função que aceite um DiaSemana e retorne se é dia útil (segunda a sexta) ou fim de semana.

    ✓ Resposta:
    enum DiaSemana {
        case Segunda;
        case Terca;
        case Quarta;
        case Quinta;
        case Sexta;
        case Sabado;
        case Domingo;
    }
    
    function ehDiaUtil(DiaSemana $dia): bool {
        return match($dia) {
            DiaSemana::Sabado, DiaSemana::Domingo => false,
            default => true,
        };
    }
    
    // Teste
    echo ehDiaUtil(DiaSemana::Segunda) ? 'Sim' : 'Não'; // Sim
    echo ehDiaUtil(DiaSemana::Sabado) ? 'Sim' : 'Não'; // Não
  2. Crie uma enum com backing string chamada Cor com as cores Vermelho, Verde e Azul, associadas aos valores 'red', 'green', 'blue'. Adicione um método hex() que retorne o código hexadecimal correspondente.

    ✓ Resposta:
    enum Cor: string {
        case Vermelho = 'red';
        case Verde = 'green';
        case Azul = 'blue';
    
        public function hex(): string {
            return match($this) {
                self::Vermelho => '#FF0000',
                self::Verde => '#00FF00',
                self::Azul => '#0000FF',
            };
        }
    }
    
    echo Cor::Vermelho->hex(); // #FF0000
  3. Implemente uma enum OperacaoMatematica com backing int para Soma (1), Subtracao (2), Multiplicacao (3) e Divisao (4). Adicione um método calcular(int $a, int $b): int|float que execute a operação.

    ✓ Resposta:
    enum OperacaoMatematica: int {
        case Soma = 1;
        case Subtracao = 2;
        case Multiplicacao = 3;
        case Divisao = 4;
    
        public function calcular(int $a, int $b): int|float {
            return match($this) {
                self::Soma => $a + $b,
                self::Subtracao => $a - $b,
                self::Multiplicacao => $a * $b,
                self::Divisao => $a / $b,
            };
        }
    }
    
    echo OperacaoMatematica::Soma->calcular(10, 5); // 15
  4. Crie uma interface PodePagar com um método pagar(): string. Implemente essa interface em uma enum MetodoPagamento com cases CartaoCredito, Boleto e Pix. Cada case deve retornar uma mensagem diferente.

    ✓ Resposta:
    interface PodePagar {
        public function pagar(): string;
    }
    
    enum MetodoPagamento: string implements PodePagar {
        case CartaoCredito = 'cartao';
        case Boleto = 'boleto';
        case Pix = 'pix';
    
        public function pagar(): string {
            return match($this) {
                self::CartaoCredito => 'Pagamento com cartão de crédito processado.',
                self::Boleto => 'Boleto gerado.',
                self::Pix => 'Pagamento via Pix realizado.',
            };
        }
    }
    
    echo MetodoPagamento::Pix->pagar(); // Pagamento via Pix realizado.
  5. Converta o seguinte uso de constantes para uma enum com backing string. As constantes são: STATUS_ATIVO = 'ativo', STATUS_INATIVO = 'inativo'. A enum deve ter um método descricao() que retorne 'Usuário ativo' ou 'Usuário inativo'.

    ✓ Resposta:
    enum StatusUsuario: string {
        case Ativo = 'ativo';
        case Inativo = 'inativo';
    
        public function descricao(): string {
            return match($this) {
                self::Ativo => 'Usuário ativo',
                self::Inativo => 'Usuário inativo',
            };
        }
    }
    
    // Uso
    echo StatusUsuario::Ativo->descricao(); // Usuário ativo