Enumerations, or enums, provide a type-safe way to represent a fixed set of possible values. This is extremely useful when dealing with states, options, or predefined categories where previously developers relied on constants, static classes, or strings.


Basic Enumerations

A basic enum defines a set of named cases. Unlike class constants, enums guarantee type-safety: you cannot pass arbitrary strings or numbers.

enum Status {
    case Draft;
    case Published;
    case Archived;
}

function printStatus(Status $status): void {
    echo "Current status: $status->name";
}

printStatus(Status::Draft);     // Current status: Draft
printStatus(Status::Published); // Current status: Published
  • Status is an enum type.
  • It contains three cases (Draft, Published, Archived).
  • The $status->name property gives the case's name as a string.

Backed Enumerations

Backed enums associate each case with a scalar value (either string or int). This is particularly useful when storing values in a database or interacting with external APIs.

enum Role: string {
    case Admin = 'admin';
    case Editor = 'editor';
    case User = 'user';
}

// Convert enum to string:
echo Role::Admin->value; // admin

// Restore enum from string:
$role = Role::from('editor');
echo $role->name; // Editor

// Safe restoration (nullable):
$role = Role::tryFrom('unknown'); 
var_dump($role); // NULL
  • ->value (the scalar).
  • from() (throws exception if invalid).
  • tryFrom() (returns null if invalid).

Enumeration Methods

Enums can have methods, just like classes. This allows encapsulating logic directly in the enum itself.

enum TrafficLight {
    case Red;
    case Yellow;
    case Green;

    public function action(): string {
        return match($this) {
            self::Red => 'Stop',
            self::Yellow => 'Slow down',
            self::Green => 'Go',
        };
    }
}

echo TrafficLight::Green->action(); // Go

This makes enums more than just constants - they can contain behavior.


Enumeration Static Methods

Static methods can also be defined inside enums, useful for custom lookup or factories.

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

    public static function fromNumber(int $num): ?self {
        return match($num) {
            1 => self::Low,
            2 => self::Medium,
            3 => self::High,
            default => null,
        };
    }
}

echo Priority::fromNumber(2)->name; // Medium

Static methods help convert external data into enums in a safe and clean way.


Enumeration Constants

Enums can declare constants, just like classes. This allows grouping helper constants with the enum definition.

enum Direction {
    case North;
    case East;
    case South;
    case West;

    public const DEFAULT = self::North;
}

echo Direction::DEFAULT->name; // North

This avoids scattering "default" values across the codebase.


Enumerations and Traits

Enums can use traits, enabling code reuse across multiple enums.

trait Printable {
    public function printName(): void {
        echo "Enum case: " . $this->name;
    }
}

enum Currency: string {
    use Printable;

    case USD = 'usd';
    case EUR = 'eur';
    case GBP = 'gbp';
}

Currency::EUR->printName(); // Enum case: EUR

This is a powerful way to keep enums DRY and consistent.


Enum Values in Constant Expressions

Enums can be used in constant expressions. This means you can define constants that reference enum cases.

enum Size {
    case Small;
    case Medium;
    case Large;
}

class Box {
    public const DEFAULT_SIZE = Size::Medium;
}

echo Box::DEFAULT_SIZE->name; // Medium

This makes enums fully usable in configuration-like contexts.


Differences from Objects

While enums are internally implemented as objects, they are singletons per case. That means:

  • You cannot new an enum case.
  • Each case is unique (=== comparison works).
  • They don't have mutable state.
$draft1 = Status::Draft;
$draft2 = Status::Draft;

var_dump($draft1 === $draft2);
// true (same singleton)

Enums behave more like "value types" than regular objects.


Value Listing

PHP provides built-in methods to list all enum cases.

  • For basic enums: cases()
  • For backed enums: still cases()
foreach (Status::cases() as $case) {
    echo $case->name;
}

// Draft
// Published
// Archived

This is extremely handy for building dropdowns, validation, or iterating over all possible states.


Serialization

Enum cases serialize as their fully qualified name.

$serialized = serialize(Status::Published);
echo $serialized;

$unserialized = unserialize($serialized);
var_dump($unserialized === Status::Published); // true

You don't need to write custom serialization logic - PHP handles it.


Why Enums Aren't Extendable

Unlike classes, enums cannot be extended. This is by design:

  • Enums define a closed set of possible cases.
  • Allowing inheritance would break the guarantee of completeness.
  • If you could extend Status and add Deleted, existing code expecting only 3 states would fail.

If you need composition, use traits or interfaces, but never inheritance.


Real-World Use Cases: User Roles and Permissions

Instead of string constants ('admin', 'editor'), use a backed enum:

enum UserRole: string {
    case Admin = 'admin';
    case Editor = 'editor';
    case Viewer = 'viewer';

    public function canEdit(): bool {
        return match($this) {
            self::Admin, self::Editor => true,
            self::Viewer => false,
        };
    }
}

$userRole = UserRole::Editor;
var_dump($userRole->canEdit()); // bool(true)

This eliminates mistakes like if ($role === 'admn').


Real-World Use Cases: Order Status in E-commerce

An order typically flows through specific states. Enums model these perfectly.

enum OrderStatus {
    case Pending;
    case Paid;
    case Shipped;
    case Delivered;
    case Cancelled;

    public function isFinal(): bool {
        return match($this) {
            self::Delivered, self::Cancelled => true,
            default => false,
        };
    }
}

$order = OrderStatus::Shipped;
var_dump($order->isFinal()); // bool(false)

This ensures no invalid state (like Refunded) sneaks in without explicit definition.


Real-World Use Cases: HTTP Methods

Enums are great for protocol-related constants.

enum HttpMethod: string {
    case GET = 'GET';
    case POST = 'POST';
    case PUT = 'PUT';
    case DELETE = 'DELETE';
}

function sendRequest(HttpMethod $method, string $url): void {
    echo "Sending {$method->value} request to $url";
}

sendRequest(HttpMethod::POST, "https://example.com/api");
// Sending POST request to https://example.com/api

Real-World Use Cases: Form Field Types

Useful for frontend-backend consistency.

enum FieldType: string {
    case Text = 'text';
    case Email = 'email';
    case Password = 'password';
    case Checkbox = 'checkbox';
}

function renderField(FieldType $type, string $name): string {
    return "<input type='{$type->value}' name='{$name}'>";
}

echo renderField(FieldType::Email, 'user_email');
// <input type="email" name="user_email">

Real-World Use Cases: Feature Flags

Enums work well for feature toggles.

enum Feature: string {
    case DarkMode = 'dark_mode';
    case BetaAccess = 'beta_access';
    case Notifications = 'notifications';
}

$enabledFeatures = [Feature::DarkMode, Feature::Notifications];

if (in_array(Feature::DarkMode, $enabledFeatures, true)) {
    echo "Dark mode enabled!";
}

Source: Orkhan Alishov's notes