Enums (PHP 8.1)
debt(d5/e3/b3/t5)
Closest to 'specialist tool catches it' (d5). The detection_hints list rector, phpstan, and psalm — all specialist static analysis tools. Common misuse patterns (using const clusters instead of enums, from() without error handling, == vs === comparisons) are not caught by default linters but are detectable by phpstan/psalm with appropriate rule sets.
Closest to 'simple parameterised fix' (e3). The quick_fix describes replacing string/int constants with a backed enum — a straightforward pattern replacement within one component. Individual mistakes like from() vs tryFrom() or == vs === are one-line fixes (e1), but migrating a full set of related constants to an enum touches multiple call sites within a component, landing closer to e3.
Closest to 'localised tax' (b3). Enums apply to web, cli, and queue-worker contexts but their scope is per-type-definition. Once defined, an enum imposes minimal ongoing burden — it actually reduces future maintenance cost by providing type safety. The debt is localised to teams unfamiliar with enum semantics rather than being a system-wide structural weight.
Closest to 'notable trap' (t5). The misconception field identifies a documented gotcha: developers familiar with class constants assume enums are just grouped constants, missing that they are proper types. The from() vs tryFrom() trap and == vs === comparison issues are well-documented surprises that most PHP developers encounter. Not a contradiction with another language's concept (which would be t7), but a notable documented gotcha warranting t5.
Also Known As
TL;DR
Explanation
PHP 8.1 enums come in two flavours: pure enums (no backing value) and backed enums (int or string backed). Backed enums support from() and tryFrom() for creating instances from raw values. Enums can implement interfaces, have methods and constants, and be used in type declarations. They eliminate the bugs associated with stringly-typed states, magic numbers, and inconsistent constant naming, and provide exhaustiveness checking in match expressions.
Common Misconception
Why It Matters
Common Mistakes
- Using backed enum values (string/int) when a pure enum would be cleaner — backed values are for serialization.
- Not implementing interfaces on enums when shared behaviour is needed — enums can implement interfaces.
- Using from() without handling the ValueError it throws for invalid values — use tryFrom() for user input.
- Comparing enum cases with == instead of === — always use strict comparison or match.
Code Examples
// Old-style string constants — no type safety or exhaustiveness:
const STATUS_ACTIVE = 'active';
const STATUS_INACTIVE = 'inactive';
function process(string $status): void { /* $status can be any string */ }
// PHP 8.1 enum:
enum Status { case Active; case Inactive; }
function process(Status $status): void { /* only valid cases accepted */ }
// Pure enum (no backing value)
enum Status { case Pending; case Paid; case Shipped; case Cancelled; }
// Backed enum — maps to string or int stored in DB
enum OrderStatus: string {
case Pending = 'pending';
case Paid = 'paid';
case Shipped = 'shipped';
// Enums can have methods
public function label(): string {
return match($this) {
self::Pending => 'Awaiting payment',
self::Paid => 'Processing',
self::Shipped => 'On the way',
};
}
}
$status = OrderStatus::from('paid'); // OrderStatus::Paid
$status = OrderStatus::tryFrom('unknown'); // null (no exception)
echo $status->label(); // 'Processing'