Lesson 85 of 100
Enums15 min5 checksPHP 8.5

Map External Values to Backed Enums

Learn how backed enums connect strings and integers to named cases, and how to choose between from() and tryFrom() for untrusted input.

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, int or string. 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 value property, and the usual name property 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.

Example
<?php
enum Suit: string
{
    case Hearts = 'H';
    case Spades = 'S';
}

echo Suit::from('H')->name, "\n";
echo Suit::Spades->value, "\n";
Output
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 throws ValueError.
  • tryFrom($value) returns the matching case or null.

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.

Example
<?php
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";
Output
["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.

Example
<?php
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'));
Output
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:

  1. Throw, because the value should never be wrong.
  2. Return null, so the caller decides.
  3. 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 Invalid when 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.

Before you start

Functions · Arrays · Types

You’ll practice
  • Use from and tryFrom with an explicit invalid-input contract
  • Implement and test a deterministic PHP 8.4 function
Your task

Make status_name() pass all 5 checks. Run your code as often as you like, then check your answer.

exercise.phpSaved in this browserPHP 8.5
CONSOLE & CHECKS● Ready

Ready when you are

Your output will appear here.
Make it work. Then make it pass.

PHP loads on your first run. Each check starts fresh. Your code stays in this browser; clearing site data removes your progress. A check or the task looks wrong? Report a problem with this exercise.