PHP 8.1 enums illustration

The problem with raw status strings

Before PHP 8.1, a “status” was usually a string bounced between the database, an API, and a switch statement. It works until it doesn’t:

  • A typo ("procesing") silently falls through to the default branch.
  • No type hint tells a teammate which values are valid.
  • Comparing two statuses means comparing fragile strings across your codebase.

Backed enums turn that loose string into a real, finite type with methods.

A backed enum with behavior

Here is a parse-safe status enum that maps raw input to a typed value and exposes a UI label:

<?php
enum OrderStatus: string
{
    case Pending    = 'pending';
    case Processing = 'processing';
    case Done       = 'done';
    case Cancelled  = 'cancelled';

    // Parse arbitrary input safely; null on anything unknown.
    public static function parse(mixed $value): ?self
    {
        if ($value instanceof self) {
            return $value;
        }
        return self::tryFrom((string) $value);
    }

    // Human-readable label for the UI.
    public function label(): string
    {
        return match ($this) {
            self::Pending    => 'Awaiting payment',
            self::Processing => 'In progress',
            self::Done       => 'Completed',
            self::Cancelled  => 'Cancelled',
        };
    }
}

Using it

$status = OrderStatus::parse($_GET['status'] ?? '');
if ($status === null) {
    http_response_code(400);
    echo 'Unknown status';
    exit;
}

echo $status->label();   // "In progress"
echo $status->value;     // "processing"

Gotchas worth remembering

  • from() throws, tryFrom() returns null. Use from() only when the value is guaranteed valid; use tryFrom() (or the parse() above) for untrusted input.
  • Enums are singletons. Compare with === or match, not by string. OrderStatus::Pending === OrderStatus::Pending is true; relying on $a->value === $b->value works but throws away the type safety.
  • Case names are identifiers. They are case-sensitive and independent of the backing value. The case is Pending, the stored value is 'pending'.
  • Backing is int or string only. You cannot mix = 1 and = 'a' in the same enum, and a backed enum cannot have a case with no value.
  • Interfaces yes, inheritance no. Enums may implement interfaces but cannot extend other enums or classes. Put shared behavior in a trait or interface instead.

One enum replaces dozens of magic strings, gives you autocomplete everywhere, and makes illegal states unrepresentable. That is a small change with an outsized payoff.