Enums
Picture a set of coins. The mint strikes one, two, five and ten rubles, and there is no three-ruble coin: a wallet can only hold one of the issued ones. A cashier does not need to check whether such a denomination exists: no others are in circulation.
Programs have closed sets like that too: a card suit, an order status, a user role. If you keep them as strings, the typo 'lvie' instead of 'live' slips through silently, and a function with a string parameter accepts any string. An enumeration solves both problems: it lists every allowed value, and it never has any others.
Pure enum
An enumeration is a type with a closed list of values, declared with the word enum (PHP 8.1). Each value in it is called a case and is declared with the word case:
<?php
enum Suit
{
case Hearts;
case Spades;
}
$card = Suit::Hearts;
echo $card->name, "\n"; // Hearts
var_dump($card === Suit::Hearts); // bool(true)
echo count(Suit::cases()), "\n"; // 2Let's walk through it step by step:
enum Suitdeclares an enumeration with two cases:HeartsandSpades.Suit::Heartsis a case. It is an object, and only one such object exists in the whole program.->namegives the case's name as a string, exactly as it is written in the code.===compares cases:$cardandSuit::Heartsare the same object, so the answer istrue.Suit::cases()returns an array of all cases in the order they were declared.
Here the coin picture falls short. There are millions of real one-ruble coins, but the case Suit::Hearts is one for the whole program. You cannot create another one with new: new Suit() gives Error: Cannot instantiate enum Suit.
An enum in match and as a type
match handles an enumeration well, and in a function declaration an enumeration works as a parameter type:
function color(Suit $s): string
{
return match ($s) {
Suit::Hearts => 'red',
Suit::Spades => 'black',
};
}
echo color(Suit::Spades), "\n"; // blackA parameter of type Suit accepts only cases of that enumeration. Calling color('Hearts') with a string gives a TypeError, so a typo does not go unnoticed. An enumeration may declare methods too, like a class: then color() moves inside Suit and works on match ($this).
Backed enum
A case of a pure enum has only a name. But an order status has to be stored in a database, sent in JSON and read from a request, and those need a scalar: a string or a number. That is what a backed enum is for: after the name you put a colon and the type string or int, and give each case a value:
enum Status: string
{
case Draft = 'draft';
case Live = 'live';
}
$s = Status::from('live');
echo $s->name, ' ', $s->value, "\n"; // Live live
var_dump(Status::tryFrom('archived')); // NULLcase Live = 'live';ties the caseLiveto the string'live'.->namestill gives the case's name,Live, and->valuegives its value,live.Status::from('live')finds a case by its value. This is how a string from the database turns back into a case.Status::tryFrom('archived')finds no such case and returnsnull.
Interviewers often ask about the difference between from and tryFrom. from throws a ValueError on an unknown value, while tryFrom calmly returns null. So tryFrom is for outside data, where an unknown value is a normal event, and from is for places where an unknown value means something is broken.
Common mistake
A string from a request was passed to from:
$input = 'archived';
$status = Status::from($input); // ✗Fatal error: Uncaught ValueError: "archived" is not a valid backing value for enum StatusA user can send any string, and the first unknown one stops the script. For input, use tryFrom and set a fallback case with ??: Status::tryFrom($input) ?? Status::Draft.
Key points
enumdefines a closed list of cases; each case is a single object, and cases are compared with===.->namegives a case's name, andcases()gives an array of all cases.- In a backed enum (
enum Status: string), each case also has a->value. from()throws aValueErroron an unknown value, whiletryFrom()returnsnull.