0

PHP 8.1 — Enums (pure and backed)

Intermediate5 min read·php-09-010

Concept

Enums (PHP 8.1) are first-class enumeration types — a set of named constants that are actual objects, not just integer or string aliases. PHP enums replace the common pattern of const STATUS_ACTIVE = 1; const STATUS_INACTIVE = 0; with a type-safe, object-oriented alternative.

Two kinds: Pure enums (no backing type) — each case is a singleton object of the enum type. Backed enums (with : int or : string) — each case has an associated primitive value, enabling serialization, database storage, and deserialization.

Pure enums (enum Status { case Active; case Inactive; }): cases are compared by identity (===). No ->value property. Cannot be created from a primitive.

Backed enums (enum Status: string { case Active = 'active'; }): each case has ->value (the backing value). Status::from('active') returns the case; Status::tryFrom('unknown') returns null instead of throwing. Useful for database columns, API responses, and form inputs.

Enum capabilities: Enums can implement interfaces, have methods, have constants, and use traits. They cannot have public mutable properties (they're immutable value types) and cannot be instantiated with new.

Why enums beat class constants: Type safety — function setStatus(Status $s) can only receive a Status enum case, not an arbitrary integer. Autocompletion — IDEs know all valid cases. Pattern matching — match($status) with no default can be verified as exhaustive by static analysis.

Code Example

php
<?php
declare(strict_types=1);

// Pure enum — no backing value
enum Direction
{
    case North;
    case South;
    case East;
    case West;

    public function opposite(): self
    {
        return match($this) {
            Direction::North => Direction::South,
            Direction::South => Direction::North,
            Direction::East  => Direction::West,
            Direction::West  => Direction::East,
        };
    }
}
echo Direction::North->opposite()->name; // "South"

// Backed enum — with string values
enum Status: string
{
    case Active    = 'active';
    case Inactive  = 'inactive';
    case Suspended = 'suspended';

    public function label(): string
    {
        return match($this) {
            self::Active    => 'Active User',
            self::Inactive  => 'Inactive Account',
            self::Suspended => 'Account Suspended',
        };
    }

    public function isUsable(): bool
    {
        return $this === self::Active;
    }
}

// Type-safe parameter
function activateUser(int $userId, Status $status): void
{
    if ($status !== Status::Active) {
        throw new \InvalidArgumentException('User must be active');
    }
}

// Database round-trip
$dbValue = 'active';                     // from database column
$status  = Status::from($dbValue);       // Status::Active (throws on invalid)
$safe    = Status::tryFrom('unknown');   // null (no throw)
echo $status->value; // "active" — back to string
echo $status->name;  // "Active" — enum case name

// All cases
$allCases = Status::cases(); // [Status::Active, Status::Inactive, Status::Suspended]
foreach ($allCases as $case) {
    echo "{$case->name}: {$case->value}\n";
}

// Enum implementing interface
interface HasColor
{
    public function color(): string;
}

enum Priority: int implements HasColor
{
    case Low    = 1;
    case Medium = 2;
    case High   = 3;

    public function color(): string
    {
        return match($this) {
            self::Low    => '#00FF00',
            self::Medium => '#FFA500',
            self::High   => '#FF0000',
        };
    }
}