Posted by Kosal
With PHP 8.1, enums have finally become a first-class citizen in the language. This long-awaited feature brings a more robust and type-safe way to handle fixed sets of values. While some developers might argue that enums should have been part of PHP years ago, it's great to see them officially integrated now.
This guide walks you through how enums work in PHP 8.1, from basic usage to more advanced capabilities.
Here's a simple example of what an enum looks like in PHP:
enum Status
{
case DRAFT;
case PUBLISHED;
case ARCHIVED;
}
Enums are essentially a collection of named constants. The power of enums lies in their ability to be type-checked, like so:
class BlogPost
{
public function __construct(
public Status $status
) {}
}
Using it is just as straightforward:
$post = new BlogPost(Status::DRAFT);
One of the powerful features of PHP enums is that you can define methods directly inside them. This becomes particularly useful in combination with the match
expression:
enum Status
{
case DRAFT;
case PUBLISHED;
case ARCHIVED;
public function color(): string
{
return match($this) {
Status::DRAFT => 'grey',
Status::PUBLISHED => 'green',
Status::ARCHIVED => 'red',
};
}
}
Usage:
$status = Status::ARCHIVED;
echo $status->color(); // Outputs 'red'
You can also define static methods within enums:
public static function make(): Status
{
// Custom factory logic
}
Using self
works as expected inside enum methods:
return match($this) {
self::DRAFT => 'grey',
self::PUBLISHED => 'green',
self::ARCHIVED => 'red',
};
Enums can implement interfaces, enabling consistent behavior across different enum types:
interface HasColor
{
public function color(): string;
}
enum Status: string implements HasColor
{
case DRAFT = 'draft';
case PUBLISHED = 'published';
case ARCHIVED = 'archived';
public function color(): string
{
// implementation here
}
}
PHP supports "backed enums", where each case is mapped to a scalar value (int
or string
):
enum Status: string
{
case DRAFT = 'draft';
case PUBLISHED = 'published';
case ARCHIVED = 'archived';
}
Or with integers:
enum Status: int
{
case DRAFT = 1;
case PUBLISHED = 2;
case ARCHIVED = 3;
}
Every case must have a value in backed enums—mixing backed and pure cases isn't allowed.
When using both a backing type and interfaces, the backing type must be declared before implements
:
enum Status: string implements HasColor
{
case DRAFT = 'draft';
case PUBLISHED = 'published';
case ARCHIVED = 'archived';
}
To serialize a backed enum, you can access its value
property:
$value = Status::PUBLISHED->value; // 'published'
To convert a value back into an enum, use:
$status = Status::from('published')
If you're unsure the value is valid, use tryFrom
to avoid exceptions:
$status = Status::tryFrom('unknown'); // returns null
Backed enums also support serialize()
, unserialize()
, and json_encode()
natively. You can customize JSON behavior via the JsonSerializable
interface.
To retrieve all possible enum cases, use cases()
:
Status::cases();
// Returns [Status::DRAFT, Status::PUBLISHED, Status::ARCHIVED
This returns an array of enum instances, which can be looped through:
foreach (Status::cases() as $status) {
echo $status->color();
}
Enums are singleton objects, so comparisons use identity:
$status1 = Status::DRAFT;
$status2 = Status::DRAFT;
var_dump($status1 === $status2); // tru
You can also check an enum's type:
$status instanceof Status; // true
Because enums are objects, you can't currently use them as array keys:
$list = [
Status::DRAFT => 'This will fail'
];
This limitation might be lifted in the future, but for now, consider using SplObjectStorage
or WeakMap
for such cases.
Traits can be used in enums but with limitations. You cannot define properties or override internal enum methods within a trait used by an enum.
PHP 8.1 introduces new reflection classes for enums:
ReflectionEnum
ReflectionEnumUnitCase
ReflectionEnumBackedCase
You can also use enum_exists()
to check if an enum is defined.
Attributes (PHP’s native annotations) can be applied to enums and their cases as well. They’re treated similarly to classes when it comes to attribute targets.
Every enum has a name
property that returns the name of the case:
echo Status::DRAFT->name; // Outputs "DRAFT"
While intended primarily for debugging, it can be useful in logging and displaying values.
Enums bring clarity, safety, and structure to your code by replacing primitive constants with well-defined objects. With features like methods, interfaces, serialization, and strict typing, they’re a welcome addition to PHP’s growing toolkit.
Whether you're modeling statuses, types, or any fixed set of options, PHP 8.1 enums help you do it better.