Enums
Only the intended values
- Added in: PHP 8.1
- What it gives you: a native mechanism for enumerated types — with clear, typed values instead of "magic strings and numbers" and endless consts.
A bit personal
This is one of my all-time favourite tools in PHP. Since they appeared, I can't imagine development without enums at all — they reduce the number of bugs, make code readable and guarantee type safety.
But talking to developers shows that far from everyone shares this excitement. So I want to share my love for them and explain why they're not just "a list of values", but a real step forward for PHP.
Types of enums
PHP has two kinds of enums.
1. Backed Enum — with a backing value (string or int)
Each case has a bound value, which lets you compare it with other values, serialize it and store it in the database, while being sure the value will always be one of those allowed by the enumeration.
enum Status: string
{
case DRAFT = 'draft';
case ACTIVE = 'active';
case ARCHIVED = 'archived';
}
What for:
- handy for statuses of orders, documents, payments, message types, employee types, etc.;
- easy to store in the database as a plain string;
- you can get the enum via
Status::from('draft'), or safely viatryFrom('draft').
2. Pure Enum — without a backing value
Just a set of unique cases without values. Used when a value isn't needed — what matters is the "state" or "mode" itself.
enum Direction
{
case NORTH;
case SOUTH;
case EAST;
case WEST;
}
What for:
- for internal logical states or operating modes;
- when nothing needs to be stored in the database;
- when a simple check like
if ($direction === Direction::NORTH)is enough.
More than just a list
Most people think an enum is just a replacement for const. In fact it's a class-like construct to which you can (and should!) add methods, behaviour, static methods and value conversions.
An enum with methods
enum Status: string
{
case DRAFT = 'draft';
case ACTIVE = 'active';
case ARCHIVED = 'archived';
// 🔹 Non-static method — for a specific value
public function label(): string
{
return match($this) {
self::DRAFT => 'Draft',
self::ACTIVE => 'Active',
self::ARCHIVED => 'Archived',
};
}
// 🔹 Static method — shared by all
public static function visible(): array
{
return [self::DRAFT, self::ACTIVE];
}
}
echo Status::ACTIVE->label(); // Active
print_r(Status::visible()); // [Status::DRAFT, Status::ACTIVE]
Another example
Statuses of documents, channels, roles:
enum Channel: string
{
case EMAIL = 'email';
case SMS = 'sms';
case TELEGRAM = 'telegram';
public function icon(): string
{
return match($this) {
self::EMAIL => '✉️',
self::SMS => '📱',
self::TELEGRAM => '💬',
};
}
public function getSenderFQCN(): string
{
return match($this) {
self::EMAIL => EmailSender::class,
self::SMS => SMSSender::class,
self::TELEGRAM => TelegramSender::class,
};
}
public static function fromString(string $value): ?self
{
return self::tryFrom($value);
}
}
Conclusion
Enums are one of the best PHP updates in recent years. They discipline the code, make the data model predictable and provide real type safety.
I can no longer imagine development without enums. And if you still don't use them in your projects — just give them a try: after a week you won't be able to write the old way.
Doctrine + Enum
I love enums so much that I even built my own package — ufo-tech/doctrine-helper, which teaches Doctrine to understand an enum in typed entity fields and to create an ENUM() column type for them in MySQL.
1. Create your PHP Enum for the entity field
<?php
namespace App;
enum MyEnum: string
{
case CASE_1 = 'My option 1';
case CASE_2 = 'My option 2';
}
2. Create your own DBAL type extending Ufo\DoctrineHelper\DBAL\AbstractEnumType
<?php
namespace App\DBAL;
use BackedEnum;
use App\MyEnum;
class MyEnumType extends AbstractEnumType
{
protected string|BackedEnum $enum = MyEnum::class;
}
3. Register the new type in Doctrine
doctrine:
dbal:
types:
App\MyEnum: 'App\DBAL\MyEnumType'
mapping_types:
enum: string
4. Use the PHP Enum to store and change the value
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use App\MyEnum;
#[ORM\Entity]
#[ORM\Table(name: 'demo')]
class Demo
{
#[ORM\Column(type: MyEnum::class, nullable: false)]
protected string $case;
public function __construct(MyEnum $case)
{
$this->case = $case->value;
}
public function changeCase(MyEnum $case): void
{
$this->case = $case->value;
}
public function getCase(): string
{
return $this->case;
}
}
This way a MySQL ENUM behaves like a full-fledged PHP Enum — with type safety, IDE autocompletion and no manual casting.
← Previous: Named arguments · Whole series · Next: readonly + accessors →
First published in Ukrainian in the Telegram channel “Created to die” on October 24, 2025; the “Doctrine + Enum” section was a separate post on the same day.