banner
RandyChan

RandyChan

深漂 / Back-end developer
github

PHP 8.1 Enums

Enumeration (Enum) allows developers to define a type as one of a set of possible discrete values. It is useful in defining domain models as it can "make invalid states unrepresentable."

In PHP, an enum is a special type of object. The Enum itself is a class, and its various entries (cases) are singleton objects of this class, meaning they are also valid objects — including type detection, and can be used wherever objects are expected.

Defining Enums#

Enums are similar to classes; they share the same namespace as classes, interfaces, and traits. They can also be autoloaded in the same way.

To define an enum, use the enum keyword and declare the enum values with the case keyword, for example, defining a post's status enum:

enum Status
{
    case DRAFT;
    case PUBLISHED;
    case ARCHIVED;
}

The benefit of enums is that they represent a collection of constant values, but most importantly, these values can be passed in like this:

class Post
{
    public function __construct(
        public Status $status, 
    ) {}
}

In this example, creating an enum and passing it to Post looks like this:

$post = new Post(Status::DRAFT);

Enum Methods#

Enums can define methods just like classes. They can be combined with the match operation. For example, to get the color corresponding to the enum value:

enum Status
{
    case DRAFT;
    case PUBLISHED;
    case ARCHIVED;
    
    public function color(): string
    {
        return match($this) 
        {
            Status::DRAFT => 'grey',   
            Status::PUBLISHED => 'green',   
            Status::ARCHIVED => 'red',   
        };
    }
}

// Calling the enum method
$status = Status::ARCHIVED;
$status->color(); // 'red'

It also supports defining static methods:

enum Status
{
    // …
    
    public static function getColor(Status $status): string
    {
        return $status->color();
    }
}

Enums also support using the self keyword to represent the current enum:

enum Status
{
    // …
    
    public function color(): string
    {
        return match($this) 
        {
            self::DRAFT => 'grey',   
            self::PUBLISHED => 'green',   
            self::ARCHIVED => 'red',   
        };
    }
}

Enum Constants#

Enum classes can include public, private, and protected constants, but since they do not support inheritance, in practice, private and protected have the same effect.

For example, defining a constant for the default status:

enum Status
{
    case DRAFT;
    case PUBLISHED;
    case ARCHIVED;

    public const DEFAULT = self::DRAFT;
}

Enum Interfaces#

Enums can also implement interfaces like classes:

interface HasColor
{
    public function color(): string;
}
enum Status implements HasColor
{
    case DRAFT;
    case PUBLISHED;
    case ARCHIVED;
    
    public function color(): string { /* implement color */ }
}

Backed Enums#

You can assign a value to each enum item, with the enum value represented by an internal object.

enum Status: string
{
    case DRAFT = 'draft';
    case PUBLISHED = 'published';
    case ARCHIVED = 'archived';
}

Note the type declaration in the enum definition. It indicates that all enum values are of the given type. You can also make it int. Only int and string are allowed as enum values.

enum Status: int
{
    case DRAFT = 1;
    case PUBLISHED = 2;
    case ARCHIVED = 3;
}

Enums with values are called "Backed enums," which can be better serialized and written to databases. If you decide to assign enum values, all enum items should have values; they cannot be mixed and matched.

Backed enums can also implement interfaces, with implements placed after the type.

enum Status: string implements HasColor
{
    case DRAFT = 'draft';
    case PUBLISHED = 'published';
    case ARCHIVED = 'archived';

    public function color(): string { /* implement color */ }
}

Serializing Enums#

Enum values support serialization and deserialization. Each enum value can be accessed through a read-only value.

$value = Status::DRAFT->value; // draft

You can also restore a value to an enum item using the Enum::from() method. The from method takes the value of the enum item, and a type mismatch or non-existent value will throw a ValueError exception.

$status = Status::from(1); // ValueError
$status = Status::from('unknown'); // ValueError
$status = Status::from('draft'); // Status::DRAFT

If you're unsure whether this enum value exists and expect null to be returned when it doesn't, you can use the Enum::tryFrom() method.

$status = Status::tryFrom('unknown'); // null
$status = Status::from('draft'); // Status::DRAFT

Enum List#

You can use the Enum::cases() method to get all enum items.

Status::cases();

/* [
    Status::DRAFT, 
    Status::PUBLISHED, 
    Status::ARCHIVED
] */

The method returns an array type containing each enum object. You can loop through all enums using this method.

array_map(
    fn(Status $status) => $status->color(), 
    Status::cases()
); // ['grey', 'green', 'red']

Enum Objects#

In fact, each enum value is a singleton object. This means you can compare enum values like this:

$statusA = Status::PENDING;
$statusB = Status::PENDING;
$statusC = Status::ARCHIVED;

$statusA === $statusB; // true
$statusA === $statusC; // false
$statusC instanceof Status; // true

Since enum values are actually objects, they cannot currently be used as array keys. The following will result in an error.

$list = [
    Status::DRAFT => 'draft', // TypeError
    // …
];

Trait#

Enums can also use traits, behaving like classes. Note that using traits in enums does not allow for properties. Only methods and static methods can be included. Traits that contain properties will cause a fatal error.

Acknowledgments#

Thank you for reading, let us explore knowledge and grow together.

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.