Enums — First-Class Enumerations (PHP 8.1)
debt(d5/e3/b3/t5)
Closest to 'specialist tool catches it' (d5). The detection_hints list phpstan and rector, with automated flagged as no and a code_pattern for class constants used as enum replacements. PHPStan can detect type-unsafe patterns and incorrect comparisons, but it requires configuration and is not a default linter — it's a specialist static analysis tool.
Closest to 'simple parameterised fix' (e3). The quick_fix states: replace class constant patterns with backed enums, use tryFrom() for user input, use in match expressions. This is a localised refactor pattern — replacing constants with enum cases is mechanical and confined to one component at a time, not a one-liner but not cross-cutting either.
Closest to 'localised tax' (b3). Applies to web, cli, and queue-worker contexts, so the scope is broad in principle, but the burden is per-use-site: each class that uses string constants instead of enums pays its own tax independently. The choice doesn't impose strong gravity on unrelated parts of the codebase.
Closest to 'notable trap' (t5). The misconception field explicitly states that developers familiar with Java/TypeScript expect enums to be typed numbers, but PHP enums are singleton objects with methods and interfaces. Additionally, the common mistakes highlight == vs === comparison and from() vs tryFrom() gotchas — these are documented but non-obvious surprises a competent developer would likely hit.
TL;DR
Explanation
Two enum types: pure (unit) enums: enum Status { case Active; case Inactive; } and backed enums: enum Status: string { case Active = 'active'; case Inactive = 'inactive'; }. Methods, interfaces, and constants in enums. Static methods. from() / tryFrom() for backed enums. cases() returns all cases. Enums extend UnitEnum (pure) or BackedEnum (backed). Cannot be instantiated with new. Cannot extend classes. Enum cases are singletons — Status::Active === Status::Active always. In match expressions: exhaustive matching. In type hints: function process(Status $status). Compare with class constants: no type safety, no first-class status.
Common Misconception
Why It Matters
Common Mistakes
- Comparing enum cases with == instead of === — always use ===.
- Using string class constants as enum replacement — loses type safety.
- Not using tryFrom() for user input — from() throws on invalid value.
Code Examples
// Class constants — no type safety:
class Status {
const ACTIVE = 'active';
const INACTIVE = 'inactive';
}
function setStatus(string $status) {} // Accepts any string
enum Status: string {
case Active = 'active';
case Inactive = 'inactive';
}
function setStatus(Status $status): void {}
setStatus(Status::Active); // Type-safe
// User input:
$status = Status::tryFrom($request->input('status')) ?? Status::Active;