Content:
PHP 8.1, released in November 2021, introduced built-in support for enums. Enums are a common feature of other programming languages, and has been a highly requested feature as a result.
In this article, we’ll take a look at how enums work in PHP, and how to use them in your code.
Introducing Enums
‘Enum’, short for ‘Enumerated Type’, is a data type containing a set of elements. A variable set to use the enum can be assigned to one of the contained elements. This limits the allowed values that can be assigned, to those defined in the enum.
This can be beneficial when working with a known set of valid values.
Consider an application which implements a typical pack of cards. There are four valid suits available – hearts, diamonds, spades and clubs. By defining these values as an enum, we can be sure that a variable using card suits must refer to one of these valid values.
This is a general overview of how an enum works, and has this same core functionality across a variety of programming languages.
PHP builds on this, adding some additional functionality.
Basic Enums
In PHP, an enum is defined similarly to a class.
class Suit {...}
enum Suit {...}
It can be thought of as a special type of class, which contains a defined set of single-use objects. These objects define the elements which define the valid set of enum values.
These objects are defined using case
statements.
enum Suit
{
case HEARTS;
case DIAMONDS;
case CLUBS;
case SPADES;
}
In this example, each of the four card suits are defined.
To use an enum element, it can be accessed similarly to a static variable in a class.
$suit = Suit::HEARTS;
They can be used in your application in a similar manner to any other object, including assignments and comparisons.
$card = new Card(suit: Suit::HEARTS);
if ($card->suit == Suit::HEARTS) {...}
They can also be used in type declarations.
class Card
{
Suit $suit;
public function setSuit(Suit $suit): void
{
$this->suit = $suit;
}
}
$card = new Card;
$card->setSuit(Suit::CLUBS);
If you’re looking to fetch all of the elements contained in an enum, you can use ::cases()
.
Suit::cases(); // [Suit::HEARTS, Suit::DIAMONDS, Suit::CLUBS, Suit::SPADES]
Elements are returned as an array.
Backed Enums
It’s possible to take things further, creating ‘backed enums’. A backed enum assigns values to each element.
enum Suit: int
{
case HEARTS = 0;
case DIAMONDS = 1;
case CLUBS = 2;
case SPADES = 3;
}
Fetching the backed value can be done by accessing the public value
attribute.
Suit::CLUBS->value; // 2
There are two ways to convert a value to the corresponding enum attribute – ::from()
, and ::tryFrom()
.
Suit::from(3); // Suit::SPADES
Suit::tryFrom(3); // Suit::SPADES
The two are very similar, differing only in their error handling. While ::from()
throws an exception if the value isn’t valid, ::tryFrom()
returns null
.
Suit::from(27); // Exception
Suit::tryFrom(27); // null
For this reason, try/catch blocks are not required – simply use ::tryFrom()
, and check for null
.
Backed enums are incredibly useful when working with data storage systems. For example, it’s commonplace to use int
s in databases. These are largely meaningless when using them in your code.
Enums are much more descriptive, and give a much clearer view of what is going on in your code.
Consider the following example.
if (Card->$suit == 1) {....}
if (Suit::from(Card->$suit) == Suit::DIAMONDS) {...}
Both examples are checking whether the suit of the current card is diamonds.
The first example relies on an integer comparison. It’s unclear what a value of 1 refers to, unless comments are added to the code. In the second example, we can clearly see that we’re comparing the suit to Suit::DIAMONDS
.
Enum Methods
Similar to classes, enums can contain methods.
enum Suit: int
{
case HEARTS = 0;
case DIAMONDS = 1;
case CLUBS = 2;
case SPADES = 3;
public function colour(): string {...}
}
It’s possible to use $this
in an enum method, to refer to the current enum instance.
Combining this with a match
statement, it’s possible to link additional values to your enum elements.
enum Suit: int
{
case HEARTS = 0;
case DIAMONDS = 1;
case CLUBS = 2;
case SPADES = 3;
public function colour(): string {
return match($this)
{
Status::HEARTS, Status::DIAMONDS => 'red',
Status::CLUBS, Status::SPADES => 'black'
};
}
}
In this example, we can fetch the colour of the current suit, by calling the colour()
method.
$suit = Suit::HEARTS;
$suit->colour(); // red
Interfaces can be applied to enums in the same way to would be applied to a class.
interface Colour
{
public function colour: string {...}
}
enum Suit: int implements Colour
{
...
}
Limitations
There are a few limitations to bare in mind when working with enums.
As enum elements are objects, they cannot be used as array keys.
$suits = {
Suit::DIAMOND => 'Diamond' // Invalid
}
Consider using an enum method if this functionality is desired.
Values used in backed enums must be either an int, or a string. It’s also an all-or-nothing choice – either all elements are backed, or none of them are. This is probably a good thing – it simplifies enum handling elsewhere in your code.
Conclusion
Enums are a great addition to PHP.
They can help to make your code more verbose, translating values from a storage system to something more readable. They’re easy to use, with much of their functionality being similar to a basic class.