PHP: How do you use enums?
Status values, priorities, traffic light colours: again and again you need a fixed list of permitted values. Since PHP 8.1 there is a language construct for that, so you no longer have to make do with class constants.
The simple form
An enum without values is called a “pure enum”:
<?php
enum Status {
case Open;
case InProgress;
case Done;
}
$s = Status::Open;
echo $s->name; // Open
?>
Every case is an object of class Status. cases() gives you all of them:
Status::cases() : Offen, InArbeit, Erledigt
That is handy for filling a select field, for instance, without maintaining the list a second time.
The form with values
As soon as the value leaves your program — towards a database, JSON or a URL — you need a “backed enum”, that is one with values attached:
<?php
enum Priority: int {
case Low = 1;
case Medium = 2;
case High = 3;
}
echo Priority::High->value; // 3
?>
int and string are the permitted types. To get from the value back to the
case you use from() or tryFrom():
Prioritaet::from(2) : Mittel
Prioritaet::tryFrom(99) : NULL
Prioritaet::from(99) : ValueError
99 is not a valid backing value for enum Prioritaet
The difference matters. from() throws an exception, tryFrom() returns
null. For values from a database that you trust, use from(). For user input,
tryFrom():
<?php
$prio = Priority::tryFrom((int) $_GET['prio']) ?? Priority::Medium;
?>
Enums can have methods
This is the point at which enums become considerably more than a list of constants:
<?php
enum TrafficLight: string {
case Red = 'red';
case Yellow = 'yellow';
case Green = 'green';
public function mayDrive(): bool {
return match($this) {
TrafficLight::Green => true,
TrafficLight::Red, TrafficLight::Yellow => false,
};
}
}
?>
Rot value=rot darfFahren=false
Gelb value=gelb darfFahren=false
Gruen value=gruen darfFahren=true
The logic now lives where the values are defined, rather than scattered across
if chains throughout half the application. Note also that match here needs
no default — if a fourth case is added later, PHP tells you instead of quietly
taking the wrong branch.
What you gain over class constants
Before PHP 8.1 you solved it like this:
<?php
class Status {
const OPEN = 'open';
const DONE = 'done';
}
function process(string $status): string {
return "processing $status";
}
?>
The problem is in the signature. The parameter is ultimately a string, so any
string at all gets through:
verarbeite gibtsnicht <- kein Fehler, der Tippfehler faellt nicht auf
With an enum as the type, the language checks:
<?php
function process(TrafficLight $t): string {
return "processing {$t->value}";
}
?>
String statt Enum -> TypeError
That is exactly the gain: an entire class of mistake disappears, because it can no longer be expressed.
Enums are singletons
Every case exists exactly once:
Status::Offen === Status::Offen => true
That is why you can compare with === and why match($this) just works. You
also cannot create an enum with new, nor clone one.
The JSON trap
A detail that is easy to trip over. A backed enum serialises to JSON without further ado, a pure enum does not:
json_encode(Ampel::Rot) : "rot"
json_encode(Status::Offen) : false
json_last_error_msg() : Non-backed enums have no default serialization
The second value is the important one: json_encode() does not throw an
exception, it returns false. If you do not check the return value you send an
empty response and look for the bug in the wrong place. With the
JSON_THROW_ON_ERROR flag you get a JsonException instead.
The practical rule that follows: as soon as a value leaves your program, use a backed enum.
Summary
- Pure enum for purely internal states, backed enum as soon as the value goes outside.
from()throws,tryFrom()returnsnull— usetryFrom()for user input.cases()returns all cases, handy for select fields.- Methods in the enum keep the logic next to the values.
- The real gain is the type check, which constants do not give you.
About Netcup (advertisement)
The German host Netcup offers, among other things, affordable and powerful web hosting packages, KVM-based root servers and dedicated servers. With our voucher codes you can save even more (6€ off your first order, 30% off all KVM-based root servers, ...).