01 · THE IDEAREAD → TRY → LEARN
Why put methods on an enum?
An enum names a closed set of values. Very soon after you define one, you need to ask questions about its cases: what label should a user see, is this state final, which case comes next? Without methods, those answers end up in scattered switch blocks and helper functions that all have to be kept in sync. An enum method keeps the rule next to the cases it describes.
Enums can hold methods, both pure enums (no values) and backed enums (string or int values). Inside a method, $this is the case the method was called on, so you can branch on it.
The match ($this) pattern
The most common method body is a match on $this. Each arm lists one or more cases and returns a result. Because match is strict and throws an error if nothing matches, it is a good fit: you cover every case, and a missing case shows up quickly instead of silently returning nothing.
enum Status: string
{
case Draft = 'draft';
case Published = 'published';
public function label(): string
{
return match ($this) {
self::Draft => 'Draft copy',
self::Published => 'Live',
};
}
}
echo Status::Draft->label(), "\n";
echo Status::from('published')->label(), "\n";Draft copy Live
Notice that the stored value ('draft') and the display label ('Draft copy') are different things. The backing value is for storage and input. The method supplies the presentation, so you can change wording in one place.
Enums and interfaces
An enum may implement an interface. Any type declaration for that interface then accepts every case of the enum. For a backed enum, the interface comes after the backing type, as in enum Suit: string implements HasColor. A pure enum simply writes enum Suit implements HasColor.
interface HasColor
{
public function color(): string;
}
enum Suit implements HasColor
{
case Hearts;
case Spades;
public function color(): string
{
return match ($this) {
self::Hearts => 'Red',
self::Spades => 'Black',
};
}
}
function paint(HasColor $item): string
{
return 'Paint: ' . $item->color();
}
echo paint(Suit::Hearts), "\n";
echo paint(Suit::Spades), "\n";Paint: Red Paint: Black
From the caller's point of view, a case behaves like any other object that has those methods.
Methods that return cases
A method may return another case of the same enum, which is a tidy way to describe a fixed cycle or a state machine. Return type self works well. Cases are singletons, so comparing with === is safe and idiomatic.
enum Light: string
{
case Red = 'red';
case Green = 'green';
case Yellow = 'yellow';
public function next(): self
{
return match ($this) {
self::Red => self::Green,
self::Green => self::Yellow,
self::Yellow => self::Red,
};
}
public function canGo(): bool
{
return $this === self::Green;
}
}
$light = Light::Red;
$steps = [];
for ($i = 0; $i < 4; $i++) {
$steps[] = [$light->value, $light->canGo()];
$light = $light->next();
}
echo json_encode($steps, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";[["red",false],["green",true],["yellow",false],["red",false]]
Things to remember
- Methods can be
public,privateorprotected. Since enums cannot be extended, private and protected behave almost the same. - You cannot define your own
cases(),from()ortryFrom(); PHP provides them. UsetryFrom()when the input might be invalid, becausefrom()throws aValueErrorfor an unknown value. - Backed-enum values are case-sensitive:
'HIGH'does not match'high'. - Keep methods small and deterministic. They should answer questions about the case, not perform input or output.
Exercise contract
Define a backed enum Priority with the cases low, normal, high and urgent, and write ticketPolicy(string $value, bool $escalate): ?array.
- Convert
$valuewithPriority::tryFrom(). If it is not a valid value, returnnull. - If
$escalateis true, move one step up: low to normal, normal to high, high to urgent. Urgent stays urgent. - Return an array with
label(the value with its first letter capitalised),hours(response target: low 72, normal 24, high 8, urgent 1) andpager(true only for high and urgent).
Put the label, hours, pager and escalation rules into enum methods, so that the function itself is only a few lines long.
Functions · Arrays · Types
- Model deterministic labels and rules with enum methods
- Implement and test a deterministic PHP 8.4 function