Before PHP 8.1, “enums” were fakes — a class full of public consts with zero type safety. Pass the wrong string and nothing complained. PHP 8.1 enums fix that: typed, comparable, and able to hold methods.

PHP 8 enums illustration

A basic backed enum

Use a backed enum when you need a stored value (database, API, JSON):

enum Status: string {
    case Draft = 'draft';
    case Published = 'published';
    case Archived = 'archived';
}

The : string means every case has a ->value. A pure enum (no backing) is just enum Status { case Draft; } when you only need the identity.

Attaching behavior

Enums are objects, so they can own methods:

enum Status: string {
    case Draft = 'draft';
    case Published = 'published';

    public function label(): string {
        return match ($this) {
            self::Draft => 'Draft',
            self::Published => 'Live',
        };
    }
}

echo Status::Published->label(); // "Live"

Type-checked call sites

The win is at the boundary — wrong values are rejected at compile/run time:

function publish(Status $status): void {
    // $status is guaranteed to be a real Status case
}

publish(Status::Published);   // ok
publish('published');         // TypeError

Gotchas

  • Compare cases with ===, not == — backed enums can otherwise match on their value.
  • Read the stored value with ->value; for pure enums there is no value, use ->name.
  • Enums are not instantiable with new, and cases are singletons.
  • match over an enum with no default is exhaustive-checked by static analyzers.

When to reach for them

Any finite set of states — order status, user roles, HTTP methods, priority levels. If you currently pass strings or ints that mean a fixed set of things, an enum is the cleaner contract.