Enums nativos no PHP 8.1
1. Introdução às Enums no PHP
1.1 O que são Enums e por que foram introduzidas no PHP 8.1
Enums (enumerações) são um tipo de dado que permite definir um conjunto fixo de valores possíveis para uma variável. Introduzidas no PHP 8.1, as Enums nativas resolveram uma lacuna histórica da linguagem, que antes dependia de soluções improvisadas com constantes de classe ou bibliotecas externas.
Antes do PHP 8.1, desenvolvedores precisavam criar classes com constantes ou usar pacotes como myclabs/php-enum para simular esse comportamento. Agora, o PHP oferece suporte nativo a Enums, trazendo tipagem forte, segurança e recursos avançados como métodos personalizados e implementação de interfaces.
1.2 Diferenças entre constantes de classe e Enums nativas
Constantes de classe são apenas valores nomeados sem qualquer estrutura de tipo. Já as Enums são tipos de primeira classe, o que significa que você pode:
- Usá-las como type hints em parâmetros e retornos
- Comparar cases com operadores
===de forma segura - Iterar sobre todos os cases possíveis
- Implementar métodos e interfaces diretamente na Enum
// Abordagem antiga com constantes
class OrderStatusConst {
const PENDING = 'pending';
const PAID = 'paid';
const SHIPPED = 'shipped';
}
// Abordagem moderna com Enum
enum OrderStatus: string {
case PENDING = 'pending';
case PAID = 'paid';
case SHIPPED = 'shipped';
}
1.3 Casos de uso clássicos
Enums são ideais para representar conjuntos finitos de valores como:
- Status de pedidos (pendente, pago, enviado, cancelado)
- Dias da semana
- Tipos de usuário (admin, moderador, usuário comum)
- Estados de máquina (ligado, desligado, espera)
- Categorias fixas de produtos
2. Sintaxe básica e declaração de Enums
2.1 Declaração de uma Enum pura
Enums puras não possuem valores associados. São úteis quando apenas a identidade do case importa.
enum Status {
case ACTIVE;
case INACTIVE;
case BANNED;
}
2.2 Declaração de uma Enum backed
Enums backed possuem valores escalares associados (string ou int), facilitando a integração com bancos de dados e APIs.
enum UserRole: string {
case ADMIN = 'admin';
case MODERATOR = 'moderator';
case USER = 'user';
}
enum HttpStatusCode: int {
case OK = 200;
case NOT_FOUND = 404;
case SERVER_ERROR = 500;
}
2.3 Métodos e constantes dentro de uma Enum
Enums podem conter métodos e constantes internas, oferecendo lógica encapsulada.
enum Color: string {
case RED = '#FF0000';
case GREEN = '#00FF00';
case BLUE = '#0000FF';
public function hexValue(): string {
return $this->value;
}
public function isWarm(): bool {
return match($this) {
self::RED => true,
default => false,
};
}
}
3. Trabalhando com cases de uma Enum
3.1 Acessando cases individuais
$status = Status::ACTIVE;
$role = UserRole::ADMIN;
3.2 Métodos estáticos: cases(), from() e tryFrom()
// Retorna array com todos os cases
$allStatus = Status::cases(); // [Status::ACTIVE, Status::INACTIVE, Status::BANNED]
// from() lança exceção se valor não existir
$role = UserRole::from('admin'); // UserRole::ADMIN
// tryFrom() retorna null se valor não existir (seguro)
$role = UserRole::tryFrom('superadmin'); // null
3.3 Iteração e comparação entre cases
foreach (Status::cases() as $status) {
echo $status->name . "\n";
}
// Comparação segura
function isActive(Status $status): bool {
return $status === Status::ACTIVE;
}
// Uso com match
$message = match($status) {
Status::ACTIVE => 'Usuário ativo',
Status::INACTIVE => 'Usuário inativo',
Status::BANNED => 'Usuário banido',
};
4. Métodos e interfaces em Enums
4.1 Implementando métodos personalizados
enum PaymentMethod: string {
case CREDIT_CARD = 'credit_card';
case PIX = 'pix';
case BOLETO = 'boleto';
public function processingFee(): float {
return match($this) {
self::CREDIT_CARD => 3.5,
self::PIX => 0.0,
self::BOLETO => 1.5,
};
}
public function label(): string {
return match($this) {
self::CREDIT_CARD => 'Cartão de Crédito',
self::PIX => 'Pix',
self::BOLETO => 'Boleto Bancário',
};
}
}
4.2 Implementando interfaces
interface HasLabel {
public function label(): string;
}
enum Priority: int implements HasLabel {
case LOW = 1;
case MEDIUM = 2;
case HIGH = 3;
case URGENT = 4;
public function label(): string {
return match($this) {
self::LOW => 'Baixa',
self::MEDIUM => 'Média',
self::HIGH => 'Alta',
self::URGENT => 'Urgente',
};
}
}
4.3 Uso em parâmetros tipados
function processOrder(OrderStatus $status): void {
// O tipo é garantido pela Enum
}
processOrder(OrderStatus::PAID); // OK
// processOrder('paid'); // Erro de tipo!
5. Enums backed e serialização
5.1 Enums com valores associados
enum OrderStatus: string {
case PENDING = 'pending';
case CONFIRMED = 'confirmed';
case SHIPPED = 'shipped';
case DELIVERED = 'delivered';
case CANCELLED = 'cancelled';
}
5.2 Métodos from() e tryFrom() para conversão segura
// Do banco de dados para Enum
$dbStatus = 'shipped';
$status = OrderStatus::from($dbStatus); // OrderStatus::SHIPPED
// Validação segura de entrada do usuário
$userInput = 'invalid_status';
$status = OrderStatus::tryFrom($userInput); // null
if ($status === null) {
throw new InvalidArgumentException('Status inválido');
}
5.3 Serialização JSON
$status = OrderStatus::SHIPPED;
echo json_encode($status); // "shipped"
// Personalizando serialização
enum OrderStatus: string implements \JsonSerializable {
case PENDING = 'pending';
public function jsonSerialize(): string {
return $this->value;
}
}
6. Enums e match expression
6.1 Uso com match para controle de fluxo
enum HttpMethod: string {
case GET = 'GET';
case POST = 'POST';
case PUT = 'PUT';
case DELETE = 'DELETE';
}
function handleRequest(HttpMethod $method): void {
$handler = match($method) {
HttpMethod::GET => 'handleGet()',
HttpMethod::POST => 'handlePost()',
HttpMethod::PUT => 'handlePut()',
HttpMethod::DELETE => 'handleDelete()',
};
echo "Chamando: $handler\n";
}
6.2 Exaustividade do match
Diferente do switch, o match com Enums pode ser exaustivo. Se você adicionar um novo case à Enum e não atualizar o match, o PHP não emitirá erro em tempo de execução, mas boas ferramentas de análise estática (PHPStan, Psalm) detectarão a falha.
6.3 Vantagens sobre switch tradicional
- Expressão (retorna valor)
- Comparação estrita (===)
- Sem fall-through acidental
- Mais legível e seguro
7. Limitações e boas práticas
7.1 Limitações
- Sem herança: Enums não podem estender outras Enums
- Sem propriedades dinâmicas: Não é possível adicionar propriedades em tempo de execução
- Sem construtor personalizado: O construtor é fixo e não pode ser alterado
- Cases são singletons: Cada case é uma instância única
7.2 Quando usar Enums vs. constantes vs. classes abstratas
- Enums: Conjuntos fixos de valores com comportamento associado
- Constantes: Valores simples sem necessidade de tipo
- Classes abstratas: Quando você precisa de hierarquia e polimorfismo
7.3 Boas práticas
- Use nomes no singular para Enums:
UserRole, nãoUserRoles - Prefira Enums backed quando precisar integrar com banco de dados
- Implemente métodos que encapsulem lógica relacionada
- Use
tryFrom()para entrada de dados não confiáveis
8. Exemplo prático completo
8.1 Criando uma Enum OrderStatus
enum OrderStatus: string {
case PENDING = 'pending';
case PAID = 'paid';
case PREPARING = 'preparing';
case SHIPPED = 'shipped';
case DELIVERED = 'delivered';
case CANCELLED = 'cancelled';
public function canTransitionTo(OrderStatus $newStatus): bool {
$allowedTransitions = [
self::PENDING->value => [self::PAID, self::CANCELLED],
self::PAID->value => [self::PREPARING, self::CANCELLED],
self::PREPARING->value => [self::SHIPPED, self::CANCELLED],
self::SHIPPED->value => [self::DELIVERED],
self::DELIVERED->value => [],
self::CANCELLED->value => [],
];
return in_array($newStatus, $allowedTransitions[$this->value] ?? []);
}
public function label(): string {
return match($this) {
self::PENDING => 'Pendente',
self::PAID => 'Pago',
self::PREPARING => 'Preparando',
self::SHIPPED => 'Enviado',
self::DELIVERED => 'Entregue',
self::CANCELLED => 'Cancelado',
};
}
}
8.2 Implementando lógica de transição
class Order {
public function __construct(
private int $id,
private OrderStatus $status = OrderStatus::PENDING
) {}
public function transitionTo(OrderStatus $newStatus): void {
if (!$this->status->canTransitionTo($newStatus)) {
throw new \InvalidArgumentException(
"Transição inválida de {$this->status->label()} para {$newStatus->label()}"
);
}
$this->status = $newStatus;
echo "Pedido #{$this->id} atualizado para: {$newStatus->label()}\n";
}
public function getStatus(): OrderStatus {
return $this->status;
}
}
8.3 Integração com banco de dados
class OrderRepository {
public function save(Order $order): void {
$statusValue = $order->getStatus()->value; // string para o banco
// INSERT INTO orders (status) VALUES (:status)
}
public function findByStatus(string $status): ?Order {
$orderStatus = OrderStatus::tryFrom($status);
if ($orderStatus === null) {
return null; // Status inválido
}
// Busca no banco...
return new Order(1, $orderStatus);
}
}
// Uso prático
$order = new Order(123);
$order->transitionTo(OrderStatus::PAID);
$order->transitionTo(OrderStatus::PREPARING);
// $order->transitionTo(OrderStatus::DELIVERED); // Lançaria exceção!
Referências
- PHP Manual: Enumerations — Documentação oficial completa sobre Enums no PHP 8.1
- PHP 8.1: Enums - stitcher.io — Artigo técnico detalhado por Brent Roose sobre implementação e uso de Enums
- PHP Enums in Depth - Laravel News — Guia prático com exemplos avançados de Enums no PHP 8.1
- Using PHP 8.1 Enums in Real Projects - Dev.to — Tutorial com casos de uso reais e boas práticas
- PHP 8.1: Backed Enums and Serialization - PHP.Watch — Análise técnica sobre Enums backed, serialização e integração com banco de dados
- Enums in PHP 8.1: A Practical Guide - Medium — Guia prático com exemplos de match expression e transição de estados