Lesson 86 of 100
Enums15 min5 checksPHP 8.5

Attach Behavior to Enum Cases

Learn how enum methods, match on $this and interfaces let each enum case carry its own labels and rules, then build a ticket policy function.

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.

Example
<?php
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";
Output
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.

Example
<?php
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";
Output
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.

Example
<?php
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";
Output
[["red",false],["green",true],["yellow",false],["red",false]]

Things to remember

  • Methods can be public, private or protected. Since enums cannot be extended, private and protected behave almost the same.
  • You cannot define your own cases(), from() or tryFrom(); PHP provides them. Use tryFrom() when the input might be invalid, because from() throws a ValueError for 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.

  1. Convert $value with Priority::tryFrom(). If it is not a valid value, return null.
  2. If $escalate is true, move one step up: low to normal, normal to high, high to urgent. Urgent stays urgent.
  3. Return an array with label (the value with its first letter capitalised), hours (response target: low 72, normal 24, high 8, urgent 1) and pager (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.

Before you start

Functions · Arrays · Types

You’ll practice
  • Model deterministic labels and rules with enum methods
  • Implement and test a deterministic PHP 8.4 function
Your task

Make ticketPolicy() pass all 5 checks. Run your code as often as you like, then check your answer.

exercise.phpSaved in this browserPHP 8.5
CONSOLE & CHECKS● Ready

Ready when you are

Your output will appear here.
Make it work. Then make it pass.

PHP loads on your first run. Each check starts fresh. Your code stays in this browser; clearing site data removes your progress. A check or the task looks wrong? Report a problem with this exercise.