Enum Methods, Interfaces & Constants (PHP 8.1)
debt(d5/e3/b3/t3)
Closest to 'specialist tool catches it' (d5). The detection_hints list phpstan and psalm as the tools that catch misuse (e.g. Enum::from() on untrusted input instead of tryFrom()). These are specialist static analysis tools, not default linters or compiler errors, placing this squarely at d5.
Closest to 'simple parameterised fix' (e3). The quick_fix describes adding a label() method, implementing interfaces, and swapping from() for tryFrom() — a small, localised refactor within the enum and its call sites. It touches a bounded set of places rather than spanning multiple unrelated files, so e3 fits better than e5.
Closest to 'localised tax' (b3). The choice applies across web, cli, and queue-worker contexts (broad applies_to), but the structural debt is confined to the enum definitions and their direct consumers. Moving logic into enum methods reduces scattered switch statements, so the ongoing maintenance tax is localised rather than codebase-wide.
Closest to 'minor surprise' (t3). The misconception is that enum methods can modify the case's value, but the immutability of enum cases is a well-documented PHP 8.1 property. Developers familiar with enums from other languages (Java, Kotlin) may be briefly surprised, but the constraint is unsurprising once learned and does not contradict deeply held intuitions — one edge case rather than a systematic trap.
Also Known As
TL;DR
Explanation
PHP 8.1 enums go beyond simple value lists. Pure enums (no backing type) and backed enums (string or int) can both implement interfaces, declare constants, and define methods. Methods have access to $this->name and $this->value. Static methods can act as factory/lookup helpers. Enums cannot have mutable properties but can use readonly static properties. Backed enums provide from() (throws on invalid value) and tryFrom() (returns null). Enums implement UnitEnum (all) and BackedEnum (backed) interfaces. Use cases: status types with behaviour (Status::Active->label()), permission sets implementing a Grantable interface, and self-describing command/event types.
Common Misconception
Why It Matters
Common Mistakes
- Putting enum-related logic in external services or switch statements instead of enum methods.
- Defining mutable state inside enums — enums are value types and must be stateless.
- Not implementing interfaces on enums — they can implement interfaces to participate in type contracts.
- Using match($enum) externally when the enum could provide the same behaviour through a method.
Code Examples
// Logic scattered outside enum:
enum Status { case Active; case Inactive; case Banned; }
function getLabel(Status $s): string {
return match($s) {
Status::Active => 'Active', Status::Inactive => 'Inactive', Status::Banned => 'Banned'
};
}
// Better: add label(): string method to the enum itself
enum Status: string implements HasLabel {
case Active = 'active';
case Inactive = 'inactive';
public function label(): string {
return match($this) {
Status::Active => 'Active User',
Status::Inactive => 'Deactivated',
};
}
}