Enums (PHP 8.1)
Reviewed & published by Brayan K
Stop passing fragile magic strings like "published" around your app. PHP 8.1 enums give you a fixed, type-safe set of named values — with their own methods — so invalid states become impossible and your editor can autocomplete every option.
Part of the free PHP course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
What You'll Learn in This Lesson
- Declare a pure enum with a fixed set of named cases
- Add scalar values with a backed enum (: string or : int)
- Convert raw values to cases with from() and tryFrom()
- Attach methods to an enum and switch on $this with match
- List every case with the built-in cases() method
- Replace magic strings with type-safe, autocompletable values
1️⃣ A Pure Enum
The simplest enum lists a handful of named cases and nothing else. Each case is a singleton object of the enum's type, so you can compare them with === and check them with instanceof. Reading ->name gives you the case name as a string. This is called a pure enum because the cases carry no scalar value.
<?php
// An enum is a type with a FIXED, named set of possible values.
// Before PHP 8.1 people used loose class constants or magic strings;
// an enum makes the whole set explicit and impossible to typo past.
enum Suit {
case Hearts; // each "case" is one allowed value
case Diamonds;
case Clubs;
case Spades;
}
// A case is a singleton object of the enum type.
$card = Suit::Hearts;
echo $card->name . "\n"; // Hearts — the case name as a string
var_dump($card === Suit::Hearts); // bool(true) — same singleton
var_dump($card instanceof Suit); // bool(true) — it's a Suit
?>2️⃣ Backed Enums
Most real-world enums need a value you can store or transmit — the string 'published' that goes in a database column, or an integer code. A backed enum declares a type after the name (: string or : int) and gives every case a value. You read it with ->value, and you convert back from a raw value using from() (throws on a bad value) or tryFrom() (returns null).
<?php
// A BACKED enum gives every case a scalar value (int OR string, never mixed).
// Perfect for the value you store in a database or send over the wire.
enum Status: string {
case Draft = 'draft';
case Published = 'published';
case Archived = 'archived';
}
$s = Status::Published;
echo $s->value . "\n"; // published — the backing value
// from() : turn a raw value INTO a case, or throw if it's invalid.
$loaded = Status::from('archived');
echo $loaded->name . "\n"; // Archived
// tryFrom(): same, but returns null instead of throwing on a bad value.
var_dump(Status::tryFrom('nope')); // NULL — no exception
?>A backed enum's values must all be the same scalar type and must be unique — two cases can't share a value, and you can't mix ints and strings. This is what makes a backed enum a perfect, lossless bridge between your code and a database column.
3️⃣ Methods on an Enum
Enums aren't just data — they can hold methods, which makes them an excellent home for behaviour that depends on the value. Inside an instance method, $this is the current case, so pairing a method with match($this) gives you clean, exhaustive logic. Enums can also have static methods (handy as named constructors), implement interfaces, and declare constants.
<?php
// Enums can hold METHODS — they're a great home for behaviour tied to a value.
// They may also implement interfaces and use constants.
enum Priority: int {
case Low = 1;
case Medium = 2;
case High = 3;
// An instance method: $this is the current case.
public function label(): string {
return match($this) {
Priority::Low => 'Low priority',
Priority::Medium => 'Medium priority',
Priority::High => 'High priority — handle now',
};
}
// A static method on the enum itself.
public static function fromScore(int $score): self {
return $score >= 8 ? self::High
: ($score >= 4 ? self::Medium : self::Low);
}
}
$p = Priority::fromScore(9);
echo $p->name . "\n"; // High
echo $p->value . "\n"; // 3
echo $p->label() . "\n"; // High priority — handle now
?>4️⃣ Listing Cases with cases()
Every enum gets a free static cases() method that returns all its cases as an array, in declaration order. This is the idiomatic way to build a <select> dropdown, validate that an input is one of the allowed options, or simply iterate over every possibility.
<?php
// Every enum gets a built-in cases() method: an array of ALL its cases,
// in declaration order. Great for building dropdowns or validating input.
enum Weekday: string {
case Mon = 'Monday';
case Tue = 'Tuesday';
case Wed = 'Wednesday';
}
foreach (Weekday::cases() as $day) {
echo $day->name . ' => ' . $day->value . "\n";
}
echo 'Total cases: ' . count(Weekday::cases()) . "\n";
?>Now you try — fill in each ___ using the 👉 hint, then run it and check against the Output panel.
<?php
// 🎯 YOUR TURN — fill in each blank marked ___ , then run it.
enum Direction: string {
case North = ___; // 👉 a string value 'N'
case South = 'S';
case East = 'E';
case West = 'W';
}
$d = Direction::___; // 👉 pick the East case
echo $d->name . "\n"; // should print: East
echo $d->value . "\n"; // should print: E
// from() should turn 'W' back into a case:
$w = Direction::___('W'); // 👉 the method that throws on a bad value
echo $w->name . "\n"; // should print: West
// ✅ Expected output:
// East
// E
// West
?>🧩 Reorder Challenge
These lines define a backed enum and use it — but they're scrambled. Put them in the order PHP needs to run without error.
B → D → C → E → A
The enum must be fully declared first: open it (B), list the case (D), then close the body (C). Only then can you reference a case (E) and read its ->value (A), which prints admin.
🧠 Quick Recall — Predict the Output
L large — ->name is the case name, ->value is the backing string.
2. What does this print?
NULL — tryFrom() returns null for an unknown value instead of throwing. (from('off') would throw a ValueError.)
3. True or false?
bool(true) — every case is a singleton, so the same case is always identical to itself with ===.
Common Errors (and the fix)
- "Uncaught ValueError: ... is not a valid backing value" — you called from() with a value no case has. For untrusted input use tryFrom() and handle the null.
- "Cannot access value on non-backed enum" — you read ->value on a pure enum. Only backed enums (: string/: int) have a value.
- "Enum case ... must not have a value" (or "must have a value") — you mixed pure and backed cases. Either every case has a value, or none does.
- "Enums may not include properties" — you tried to give a case mutable state. Use a method returning match($this) instead of a property.
Pro Tips
- 💡 Type-hint with the enum. A parameter function setStatus(Status $s) can only ever receive a valid case — invalid states become unrepresentable.
- 💡 Use tryFrom($input) ?? Default::Case to safely turn request data into an enum with a sensible fallback in one line.
- 💡 Keep match($this) exhaustive. No default arm means adding a new case forces you to update the method — the compiler reminds you.
📋 Quick Reference — Enums
| Syntax | Example | What It Does |
|---|---|---|
| enum X { ... } | enum Suit { case Hearts; } | Declare a pure enum |
| enum X: string | case Draft = 'draft'; | Backed enum with values |
| ->name | Status::Draft->name | Case name as a string |
| ->value | Status::Draft->value | Backing value (backed only) |
| X::from() | Status::from('draft') | Value → case (throws if bad) |
| X::tryFrom() | Status::tryFrom($x) | Value → case or null |
| X::cases() | Status::cases() | Array of all cases |
Mini-Challenge: HTTP Status Helper
No code is filled in this time — just a brief and an outline. Write it yourself, run it on onecompiler.com/php or your own machine, then check your result against the expected output in the comments.
<?php
// 🎯 MINI-CHALLENGE: an HTTP status helper.
// No code is filled in — work from the steps below, then run it.
//
// 1. Make a BACKED enum HttpStatus: int with cases:
// Ok = 200, NotFound = 404, ServerError = 500
// 2. Add a method isError(): bool that returns true when the
// backing value is >= 400 (use $this->value).
// 3. Add a method message(): string using match($this) to map
// each case to text, e.g. Ok => 'OK', NotFound => 'Not Found'.
// 4. Take HttpStatus::from(404), then echo:
// Code: 404
// Message: Not Found
// Error?: yes
//
// ✅ Expected output:
// Code: 404
// Message: Not Found
// Error?: yes
// your code here
?>🎉 Lesson Complete!
- ✅ A pure enum is a fixed set of named, singleton cases
- ✅ A backed enum (: string/: int) attaches a scalar ->value to each case
- ✅ from() throws on a bad value; tryFrom() returns null
- ✅ Enums can hold methods — switch on $this with match
- ✅ cases() returns every case for dropdowns and validation
Practice quiz
What is the difference between a pure enum and a backed enum?
- A pure enum is faster
- A pure enum can hold properties
- A backed enum gives every case a scalar value (->value); a pure enum has only ->name
- There is no difference in PHP 8.1
Answer: A backed enum gives every case a scalar value (->value); a pure enum has only ->name. A backed enum (enum Status: string) attaches an int or string value to each case, exposed via ->value. A pure enum's cases are just named singletons with ->name.
Given enum Size: string { case S = 'small'; case L = 'large'; }, what does Size::L->name . ' ' . Size::L->value print?
- L large
- large large
- L L
- large L
Answer: L large. ->name is the case name (L) and ->value is the backing string (large), so it prints 'L large'.
What does Status::tryFrom('off') return when 'off' is not a valid backing value?
- false
- An empty string
- It throws a ValueError
- null
Answer: null. tryFrom() returns null for an unknown value instead of throwing, making it ideal for untrusted input.
What does from() do when given a value no case has?
- Returns null
- Throws a ValueError
- Returns the first case
- Returns false
Answer: Throws a ValueError. from() throws a ValueError on an invalid value — use it when an invalid value is a bug that should stop execution.
Which built-in method returns an array of all cases in declaration order?
- cases()
- all()
- values()
- toArray()
Answer: cases(). Every enum gets a static cases() method returning all its case objects in declaration order — great for dropdowns and validation.
What must be true about the backing values of a backed enum?
- They can mix ints and strings freely
- They must be sequential integers
- They must all be the same scalar type and be unique
- They are optional
Answer: They must all be the same scalar type and be unique. A backed enum's values must all be the same scalar type (all int or all string) and must be unique — no two cases can share a value.
How do you add per-case behaviour to an enum?
- Give each case a mutable property
- Define a method that uses match($this) to return the right value
- Subclass the enum per case
- Enums cannot have behaviour
Answer: Define a method that uses match($this) to return the right value. Enums can't hold mutable per-case state, but they can have methods. A common pattern is match($this) inside a method to map each case to a value.
Inside an enum's instance method, what does $this refer to?
- The enum class
- The backing value
- Always the first case
- The current case
Answer: The current case. In an instance method, $this is the current case, which is why pairing it with match($this) gives clean per-case logic.
For a pure enum Suit { case Hearts; }, what is var_dump(Suit::Hearts === Suit::Hearts)?
- bool(false)
- bool(true)
- It throws an error
- NULL
Answer: bool(true). Each case is a singleton object, so the same case is always identical to itself under ===, giving bool(true).
Which safe one-liner turns request data into an enum with a default fallback?
- Status::from($input) ?? Status::Draft
- Status::cases()[$input]
- Status::tryFrom($input) ?? Status::Draft
- new Status($input)
Answer: Status::tryFrom($input) ?? Status::Draft. tryFrom() returns null on an invalid value, so the ?? operator can supply a sensible default in one line.
Continue this course
- Previous: Object-Oriented PHP
- Next: Traits — Share methods across unrelated classes with horizontal code reuse
- Quick reference: PHP cheat sheet
Frequently asked questions
What's the difference between a pure enum and a backed enum in PHP?
A pure enum (enum Suit { case Hearts; }) has cases that are just named singleton objects with no scalar value — you can read ->name but there is no ->value. A backed enum (enum Status: string { case Draft = 'draft'; }) attaches an int or string to every case, exposes ->value, and unlocks from()/tryFrom() for converting raw values into cases. Use a backed enum whenever the value must be stored in a database, sent in JSON, or read from a form; use a pure enum when the set of names is all you need.
When should I use from() versus tryFrom()?
Both convert a raw scalar into an enum case. from() throws a ValueError if the value doesn't match any case — use it when an invalid value is a bug that should stop execution. tryFrom() returns null instead of throwing — use it for untrusted input (a query string, a form field) where you want to handle the 'not a valid value' case gracefully, for example return a 400 response. A common pattern is $status = Status::tryFrom($input) ?? Status::Draft to fall back to a default.
Can a PHP enum have properties to store extra data per case?
An enum case cannot have its own mutable properties — enums are not allowed to hold state, which is what keeps each case a true singleton. If you need extra data per case, expose it through a method that uses match($this) to return the right value, or implement an interface and define a method like color() or label(). The backing value (->value) is the one piece of scalar data a case carries directly.
How do I list every value of an enum?
Every enum automatically has a static cases() method that returns an array of all its case objects in declaration order. Loop over Weekday::cases() to build a dropdown, validate input, or count options. For a backed enum you can read ->value and ->name on each item. There is no built-in values()-only method, but array_map(fn($c) => $c->value, Weekday::cases()) gives you a plain array of the backing values.