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ão UserRoles
  • 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