Digging deeper into PHP 8.1 enums

Nov 30, 2021 by Jeroen Deviaene

Enumerations is a way to create a native type that has a limited number of possible values. An easy example of this could be the status of an order, possible values for this could be Preparing, Packaged, Sent, and Delivered. To easily limit these statuses, we could create an enum in PHP8.1 as shown below and use this as the type for the $status property on our order.

enum OrderStatus {
    case Preparing;
    case Packaged;
    case Sent;
    case Delivered;
    case Canceled;
}
class Order {
    public int $id;
    public OrderStatus $status;
}

$order = new Order();
$order->status = OrderStatus::Preparing;

Types of enums

PHP 8.1 introduces some interfaces that add functionality to the enums. The example above is called a UnitEnum, which is the most basic enum. Each enum object extends from this interface. A bare UnitEnum can be used to compare against, but it cannot be stored because it is not possible to cast an enum to a primitive type.

An extension from the UnitEnum is a BackedEnum. This type of enum allows you to add a value to each of the enum cases. In doing this, the enum values can be converted into a primitive type that can be stored in a file or a database. Below is an example of how we can make a BackedEnum from the OrderStatus enum.

enum OrderStatus: string {
    case Preparing = 'Preparing';
    case Packaged = 'Packaged';
    case Sent = 'Sent';
    case Delivered = 'Delivered';
}

Notice the string type added to the enum declaration. It is always required to set the value type for a BackedEnum. Currently, PHP 8.1 only allows string or int to be used as the value type.

A BackedEnum also comes with a value property to convert an enum object to its accompanying primitive value.
Also, there are two functions from() and tryFrom() to turn the primitive value back into an enum object. The difference between these two is when using from() with a value that is not known in one of the enum cases, this function will throw a ValueError. Whereas tryFrom() will simply return null.

Comparing enums

The important thing to know here is that enum objects are singletons, meaning each enum value object has the same reference. Because of this, enums can be compared against each other directly without having to get their underlying primitive value first.

$statusPreparing = OrderStatus::Preparing;

OrderStatus::Preparing === $statusPreparing;    // true
OrderStatus::Sent === $statusPreparing;         // false
in_array($statusPreparing, [OrderStatus::Preparing, OrderStatus::Packaged]);    // true
in_array($statusPreparing, [OrderStatus::Sent, OrderStatus::Delivered]);        // false

Storing enums

As mentioned before, it is not possible to cast any enum directly to a primitive value. Not even a BackedEnum.
To get the string or integer value of an enum, you always have to use the value property of the enum.

(string) OrderStatus::Preparing;    // PHP Error: Object of class OrderStatus could not be converted to string
OrderStatus::Preparing->value;      // "Preparing"

When you want to turn this primitive value back into an enum, use the from method explained previously.

$order = new Order();
$order->status = OrderStatus::from('Preparing');

Closing thoughts

Hopefully, these explanations helped you to get started with enumerations. If you want an even more technical explanation of what is possible with enums, you can read the full rfc for this feature on php.net.