
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; usetryFrom()(or theparse()above) for untrusted input. - Enums are singletons. Compare with
===ormatch, not by string.OrderStatus::Pending === OrderStatus::Pendingis true; relying on$a->value === $b->valueworks 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
= 1and= '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.