01 · THE IDEAREAD → TRY → LEARN
The problem enums solve
Many values in a program come from a small, closed list: an order is draft, published or archived, a card is a heart or a spade. Strings such as 'published' work, but a typo like 'publised' is still a valid string, so mistakes slip through quietly. An enum defines a new type with a fixed number of legal values. Anything outside that list simply is not an instance of the type.
Pure enums
An enum is declared with enum, and each legal value is a case. Enums share namespaces with classes, and each case is a single shared object of the enum type. That has some consequences:
- Two references to the same case are identical under
===. - A case is never equal to a plain string with the same name.
- Every case has a read-only
nameproperty holding its case-sensitive name. - Comparisons with
<and>are not meaningful for enum cases.
enum Suit
{
case Hearts;
case Spades;
}
$a = Suit::Spades;
$b = Suit::Spades;
echo $a->name, "\n";
echo json_encode($a === $b), "\n";
echo json_encode($a === 'Spades'), "\n";Spades true false
An enum whose cases carry no extra data is called a pure enum. It is a good fit when you only need to tell the options apart inside your own code.
Backed enums
Data often has to leave your program, for example in JSON, a form field or a stored record. For that, give the enum a scalar type and assign each case a value. This is a backed enum. Every case must have a value of the declared type (string or int), and values must be unique.
A backed enum gives you three useful tools:
->valuereads the scalar behind a case.Status::from($x)returns the matching case, or throwsValueErrorif there is none.Status::tryFrom($x)returns the matching case, ornullif there is none.Status::cases()returns a list of all cases in declaration order.
enum Status: string
{
case Draft = 'draft';
case Published = 'published';
}
echo Status::Draft->value, "\n";
echo Status::from('published')->name, "\n";
var_dump(Status::tryFrom('missing'));
echo json_encode(array_map(fn(Status $s) => $s->value, Status::cases())), "\n";draft Published NULL ["draft","published"]
Choosing from() or tryFrom()
Use from() when an unknown value means a bug, such as reading data your own code wrote. Use tryFrom() when the input is untrusted and an unknown value is an ordinary situation you want to handle. The nullsafe operator pairs nicely with it: $status?->value ?? 'none' handles the null case in one expression.
Normalise before converting. Matching is exact, so 'Draft' and ' draft' do not match 'draft'. Trimming and lowercasing the input first is a common step.
Type checking with enums
When a function parameter is typed as an enum, only cases of that enum can be passed. A string that merely looks right is rejected with a TypeError. This moves validation to the edge of your program: convert once, then pass real enum values everywhere else.
enum Status: string
{
case Draft = 'draft';
case Published = 'published';
}
function describe(Status $status): string
{
return 'Status is ' . $status->value;
}
echo describe(Status::Published), "\n";
try {
describe('draft');
} catch (TypeError $e) {
echo "TypeError\n";
}
try {
Status::from('nope');
} catch (ValueError $e) {
echo "ValueError\n";
}Status is published TypeError ValueError
Adding behaviour
Because enums are class-like, they can have methods. A label() method that uses match ($this) keeps the display text next to the cases it describes, so there is one place to update when a case is added.
Exercise
Write statusLabel(string $input): string. The Status enum with cases Draft, Published and Archived (backed by 'draft', 'published', 'archived') is already in the starter. Trim and lowercase the input, convert it with tryFrom(), and return the case's label ('Draft', 'Published' or 'Archived'). For anything that is not a known status, including an empty string, return 'Unknown'. Tests check the returned string.
Recap
- An enum is a type with a closed set of cases; each case is a single object.
- Cases are not equal to strings, and they have a
nameproperty. - A backed enum adds
value,from(),tryFrom()andcases(). - Use
tryFrom()for untrusted input and handlenull. - Typed parameters reject anything that is not a case of the enum.
Classes
- Define a backed enum
- Safely convert input to a known enum value