Skip to content
Theory · Inheritance and interfaces · lesson 7 of 8

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
<?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"; // 2

Let's walk through it step by step:

  1. enum Suit declares an enumeration with two cases: Hearts and Spades.
  2. Suit::Hearts is a case. It is an object, and only one such object exists in the whole program.
  3. ->name gives the case's name as a string, exactly as it is written in the code.
  4. === compares cases: $card and Suit::Hearts are the same object, so the answer is true.
  5. 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:

PHP
function color(Suit $s): string
{
    return match ($s) {
        Suit::Hearts => 'red',
        Suit::Spades => 'black',
    };
}

echo color(Suit::Spades), "\n"; // black

A 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:

PHP
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')); // NULL
  1. case Live = 'live'; ties the case Live to the string 'live'.
  2. ->name still gives the case's name, Live, and ->value gives its value, live.
  3. Status::from('live') finds a case by its value. This is how a string from the database turns back into a case.
  4. Status::tryFrom('archived') finds no such case and returns null.

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:

PHP
$input = 'archived';
$status = Status::from($input); // ✗
TEXT
Fatal error: Uncaught ValueError: "archived" is not a valid backing value for enum Status

A 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

  • enum defines a closed list of cases; each case is a single object, and cases are compared with ===.
  • ->name gives a case's name, and cases() gives an array of all cases.
  • In a backed enum (enum Status: string), each case also has a ->value.
  • from() throws a ValueError on an unknown value, while tryFrom() returns null.