Enums, short for enumerations, are a powerful feature introduced in PHP 8.1 that allow developers to define a set of named constants. They represent a significant improvement in PHP’s type system, offering a way to create more expressive, self-documenting, and type-safe code. In this article, we will take a deep dive into enums in PHP, exploring their benefits, use cases, and how they can dramatically improve your code quality and maintainability.
What are Enums?
Enums are a special kind of class used to represent a fixed set of constant values. These values are typically used to represent a collection of related items, states, or options within a system. For example, you might use an enum to represent the days of the week, card suits in a deck, or status codes in an application. Key enums characteristics include:
- Type safety: Enums provide compile-time checking, ensuring that only valid enum values are used.
- Self-documentation: The enum’s name and cases clearly communicate the intent and possible values.
- Extensibility: Enums can be enriched with methods and properties, allowing for more complex behavior.
IPC NEWSLETTER
All news about PHP and web development
Before Enums in PHP
Before PHP 8.1, there was no built-in support for enums in PHP. Developers had to use constants or classes to define state and workflow. This approach was not ideal as can see in the following example, where most of the work needs to be done manually for any validation or transformation.
class Status { const PENDING = 'pending'; const APPROVED = 'approved'; const REJECTED = 'rejected'; public function __construct($status) { /*...*/ } public function all() { return [ self::PENDING, self::APPROVED, self::REJECTED ]; } }
Other forms of implementation include using interfaces, abstract classes, traits, or even arrays. However, these approaches are not as clean and readable as enums.
// Arrays $status = [ 'PENDING' => 'pending', 'APPROVED' => 'approved', 'REJECTED' => 'rejected' ]; // Interfaces, traits or abstract classes interface Status { const PENDING = 'pending'; const APPROVED = 'approved'; const REJECTED = 'rejected'; } // SplEnum (https://pecl.php.net/package/SPL_Types - not supported since php 7) class Status extends SplEnum { const PENDING = 'pending'; const APPROVED = 'approved'; const REJECTED = 'rejected'; }
YOU LOVE PHP?
Explore the PHP Core Track
Pure Enums or Backed Enums
There are two types of enums in PHP: Pure Enums and Backed Enums. In the first case, pure enums are a set of named constants without a value associated with them. In the second case, backed enums are a set of named constants with a value scalar type associated with them.
Example:
// Pure Enum enum Status { case Pending; case Approved; case Rejected; } // Backed Enum enum Status: string { case Pending = 'pending'; case Approved = 'approved'; case Rejected = 'rejected'; }
“When should I use one over the other?”, you might be asking. If you need to store the value or retrieve and build your enum from a value, you should use a backed enum. If you just need a set of named constants to represent a temporary state or a workflow, pure enums are the way to go.
Using Enums
We have a few ways to use enums in PHP. The first one is by using the match expression. This is a new feature in PHP 8.0 that allows you to match a value against a set of patterns. In the case of enums, you can match a value against the enum cases.
enum Status { case Pending; case Approved; case Rejected; } class Order { public Status $status; public function __construct(Status $status) { $this->status = $status; } public function approve() { $this->status = Status::Approved; } public function process() { match($this->status) { Status::Pending => $this->processPending(), Status::Approved => $this->processApproved(), Status::Rejected => $this->processRejected(), }; } }
Accessing enum values
It’s important to note that when retrieving the values of the enum, this method ::ATTR will return the instance of the enum, not the value itself and because of that it will throw an exception in case you try to use it as a string. To get the value, you need to call the ->value method.
Unfortunately, you can’t use either __toString or __invoke magic methods to get the value directly. See https://wiki.php.net/rfc/enumerations#magic_read-methods.
enum Status: string { case Pending = 'pending'; case Approved = 'approved'; case Rejected = 'rejected'; } echo Status::Pending; // Fatal error: Uncaught Error: Object of class Status could not be converted to string ... echo Status::Pending->value; // pending echo Status::Pending->name; // Pending (the name of the case)
Enriched Enums
You can also enrich your enums with methods that can be called on each case. This is a great way to encapsulate logic. In the next example we will create a VirtualQueueStatus enum also including methods to retrieve the title and the css class for each status.
enum VirtualQueueStatus: string { case BeingServed = 'being_server'; case Completed = 'completed'; case NoShow = 'no_show'; case Removed = 'removed'; case Abandoned = 'abandoned'; public function title(): string { return match ($this) { self::BeingServed => 'Being served', self::Completed => 'Completed', self::NoShow => 'No show', self::Removed => 'Removed', self::Abandoned => 'User left', }; } public function cssClass(): string { return match ($this) { self::BeingServed => 'bg-yellow-200 text-yellow-700', self::Completed => 'bg-green-200 text-green-700', self::NoShow => 'bg-red-200 text-red-700', self::Removed => 'bg-red-200 text-red-700', self::Abandoned => 'bg-red-200 text-red-700', }; } } class MyQueue { public function __construct(public VirtualQueueStatus $status) {} } $status = 'being_server'; // from database $queue = new MyQueue(status: VirtualQueueStatus::from($status)); echo $queue->status->title(); // Being served
Using ::from method, you can build your enum from a value. This is a great way to hydrate your enums from a database, where you almost certainly have a valid value. But when building from a user’s input you may use a ::tryFrom method that will return null if the value is not valid.
$status = 'anything'; // from user input // in case of invalid value, it will return null and the default value will be set $queue = new MyQueue(status: VirtualQueueStatus::tryFrom($status) ?? VirtualQueueStatus::BeingServed); echo $queue->status->title(); // Being served Another great usage of enums is to list all possible values for a given scenario, like a dropdown in a form. // ['being_server', 'completed', 'no_show', 'removed', 'abandoned'] foreach (VirtualQueueStatus::all() as $status) { echo $status; // being_server, completed, no_show, removed, abandoned }
Let’s implement another method where we can check if this is a final state and another one to retrieve only error states.
enum VirtualQueueStatus: string { /* ... */ public static function isFinal(VirtualQueueStatus $status): bool { return match ($status) { self::Completed, self::NoShow, self::Removed, self::Abandoned => true, default => false, }; } public static function errorStates(): array { return [ self::NoShow, self::Removed, self::Abandoned, ]; } } foreach (VirtualQueueStatus::errorStates() as $status) { echo $status; // no_show, removed, abandoned }
IPC NEWSLETTER
All news about PHP and web development
Conclusion
Powerful and flexible, enums are a great addition to PHP. You can use enums anywhere you need a set of named constants, in my team we are constantly replacing old constants with enums. They are a great way to make your code more readable and maintainable.
You don’t need to worry about the performance of enums, as they are compiled to a very efficient structure just like any other class object internally.
Many of the examples in this article are inspired by the official documentation and real work projects, you can find more information in the official documentation or in the RFC.