01 · THE IDEAREAD → TRY → LEARN
Why backed enums exist
A pure enum case is just a named singleton object. That is ideal inside your program, but data often has to leave it: a form field, a JSON payload, a stored status code. A backed enum gives every case a scalar partner, either an int or a string, so a case can be written out and read back.
A backed enum has these rules:
- The enum declares one backing type,
intorstring. A union is not allowed. - Every case must declare its own value. PHP never generates values for you.
- Values must be unique within the enum.
- Each case has a read-only
valueproperty, and the usualnameproperty holds the case label.
Reading a case and its value
The first example shows both directions. Suit::from('H') turns a scalar into a case, and ->value turns a case back into its scalar.
enum Suit: string
{
case Hearts = 'H';
case Spades = 'S';
}
echo Suit::from('H')->name, "\n";
echo Suit::Spades->value, "\n";Hearts S
Because value is read-only, you cannot change it or take a reference to it. Treat it as fixed data attached to the case.
from() and tryFrom()
Every backed enum automatically gets two static methods, and you may not define methods with these names yourself.
from($value)returns the matching case or throwsValueError.tryFrom($value)returns the matching case ornull.
The difference is a contract about trust. Use from() when a missing match means your program or data is broken and should stop loudly. Use tryFrom() when the input is untrusted and invalid values are a normal outcome that you want to handle, perhaps with a default or an error message.
The nullsafe operator combines well with tryFrom(). In the next example, ?->name gives null when no case matched, and ?? replaces that with a fallback label.
enum Level: int
{
case Low = 1;
case High = 3;
}
$names = [];
foreach ([1, 2, 3] as $number) {
$names[] = Level::tryFrom($number)?->name ?? 'unknown';
}
echo json_encode($names, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";["Low","unknown","High"]
The value 2 is not backed by any case, so it becomes unknown rather than an error.
Seeing both failure styles
The third example makes the contrast explicit. The from() call throws, and we catch it by class. The tryFrom() call returns null, which var_dump() prints as NULL.
enum Suit: string
{
case Hearts = 'H';
case Spades = 'S';
}
try {
Suit::from('X');
} catch (ValueError $e) {
echo $e::class, "\n";
}
var_dump(Suit::tryFrom('X'));ValueError NULL
Matching is exact
Backing values are compared as given. 'h' does not match 'H', and ' H' with a leading space does not match either. If your external data may carry stray whitespace or different letter case, normalize it before the lookup. Keep that normalization in one place so the rest of the program only deals with real cases.
Type juggling also matters. In weak typing mode PHP may coerce a compatible scalar, but in strict mode passing an int to a string-backed enum raises TypeError, as does passing a float. In weak mode compatible scalars may be coerced; lossy float-to-int conversion can emit a deprecation notice. Declare your parameter types so the conversion rules are clear.
Designing the invalid-input contract
Before you write a function around an enum, decide what invalid input means. Three common answers:
- Throw, because the value should never be wrong.
- Return
null, so the caller decides. - Return a documented fallback such as a default case or a label.
Write the choice down in the function description, and test it with a bad value, an empty string and a value that only differs by case or spacing.
Exercise contract
Define status_name(string $input): string using the Status enum from the starter code. The function should:
- Trim surrounding whitespace and lowercase the input with ASCII functions.
- Look the result up as a backing value of
Status. - Return the matching case name, for example
Published. - Return exactly
Invalidwhen no case matches, including for an empty string. - Never throw for any string input.
The tests call the function with plain strings and compare the returned strings, so keep printing out of the function.
Recap
Backed enums attach an int or string to each case. Use ->value to go out, from() when failure should be an exception, and tryFrom() when failure is expected input. Normalize messy external data first, then do one strict lookup.
Functions · Arrays · Types
- Use from and tryFrom with an explicit invalid-input contract
- Implement and test a deterministic PHP 8.4 function