Enum::cases() & Enum from()/tryFrom()
debt(d5/e1/b3/t7)
Closest to 'specialist tool catches it' (d5). The detection_hints list phpstan and psalm as the tools that catch misuse (e.g., calling tryFrom() on a unit enum or from() on untrusted input). These are static analysis tools, not the default compiler or a basic linter, placing this squarely at d5.
Closest to 'one-line patch or single-call swap' (e1). The quick_fix is a direct substitution: replace from() with tryFrom() for user input, or swap a unit enum for a backed enum. Each correction is a single-call or single-declaration change.
Closest to 'localised tax' (b3). The choice affects wherever enum values are consumed from external input or used for form options, but the rest of the codebase is unaffected. The fix is scoped to individual call sites rather than being architectural or cross-cutting.
Closest to 'serious trap' (t7). The misconception is that from()/tryFrom() exist on all enums, but they only exist on backed enums. A developer familiar with other language enums or even PHP's own unit enums would confidently call tryFrom() on a unit enum and get a fatal error at runtime. This contradicts the intuitive expectation that all enum variants share the same interface, earning t7.
TL;DR
Explanation
UnitEnum::cases() returns an array of all enum cases. BackedEnum::from(string|int) returns the matching case or throws ValueError. BackedEnum::tryFrom() returns null instead of throwing. Use cases() to build select options, from() when the value should always be valid, tryFrom() when handling user input that may be invalid. Pure enums only have cases() — no from()/tryFrom(). PHP 8.3 added typed enum constants.
Common Misconception
Why It Matters
Common Mistakes
- Using from() on untrusted user input — throws ValueError.
- Calling tryFrom() on a pure (unit) enum — method doesn't exist.
- Not using cases() for building form options — manually listing cases duplicates the enum.
Code Examples
// Unsafe — throws ValueError on invalid input:
$status = Status::from($request->get('status'));
// Building options manually — duplicates enum:
$options = ['active' => 'Active', 'inactive' => 'Inactive'];
// Safe user input:
$status = Status::tryFrom($request->get('status')) ?? Status::Active;
// Build options from enum:
$options = array_column(
array_map(fn(Status $s) => ['value' => $s->value, 'label' => $s->getLabel()], Status::cases()),
'label', 'value'
);