PHP 8.1: Enums, Readonly, Fibers — Finally
· Jerwin Arnado
Archive note: this is a backdated post, written years later while rebuilding this site. It’s dated to the moment it covers, but the hindsight is real.
PHP 8.1 landed on November 25, and it contains the single most-requested language feature I can remember: real enums. After last year’s 8.0 modernized the syntax, 8.1 goes after the patterns we’ve been faking with class constants and packages for a decade.
Enums, at last
Every PHP codebase has this somewhere:
class OrderStatus
{
const PENDING = 'pending';
const PAID = 'paid';
const SHIPPED = 'shipped';
}
…and every such codebase has a bug where someone passed 'Paid' or 'completed' and nothing complained until production. Native enums close that hole at the type level:
enum OrderStatus: string
{
case Pending = 'pending';
case Paid = 'paid';
case Shipped = 'shipped';
public function label(): string
{
return match($this) {
self::Pending => 'Awaiting payment',
self::Paid => 'Paid',
self::Shipped => 'On the way',
};
}
}
function ship(OrderStatus $status) { /* only real statuses get in */ }
Backed enums (: string/: int) handle the database round-trip via OrderStatus::from($row->status) and ->value, methods hang behavior directly on the cases, and match + enums means the engine yells when you forget a case. Laravel’s $casts support for enums is already landing in the framework — Eloquent attributes that hydrate straight into enum instances. Delete your status-string helper traits; that era is over.
Readonly properties
Write-once properties, enforced by the engine:
class Money
{
public function __construct(
public readonly int $amount,
public readonly string $currency,
) {}
}
Combined with 8.0’s constructor promotion, proper immutable value objects are now two lines of ceremony instead of a class full of getters. DTOs, money types, coordinates — anything that should never mutate after construction now says so in the signature instead of a docblock plea.
Fibers (the quiet one)
Fibers bring full coroutine support — pausable, resumable blocks of execution. Honest framing: application devs won’t touch fibers directly, and PHP didn’t suddenly become Node. Fibers are plumbing for the async frameworks (ReactPHP, Amp) to offer async I/O without the contagious Promise/->then() style — synchronous-looking code that suspends under the hood. Whether the wider PHP world adopts async remains an open question, but the language-level excuse is gone.
The rest of the goodie bag
neverreturn type — for methods that always throw or exit; static analysis gets smarter.- First-class callable syntax —
$fn = strlen(...);replacesClosure::fromCallable('strlen'). newin initializers — default parameter values can finally be objects:function __construct(private Logger $logger = new NullLogger()) {}.- Pure intersection types —
Countable&Traversablefor when a value must be both. array_is_list()— settles a JSON-encoding guessing game we’ve all played.
Upgrade posture
Same advice as every year, refined by experience: let your dependencies declare 8.1 support, then move — the upgrade from 8.0 is mild, and enums alone repay the effort. The yearly release cadence has quietly turned PHP into a language that improves predictably, which after the 5.6 → 7.0 desert still feels like luxury.
The decade-old jokes about PHP are now mostly about a language that no longer exists. Enums were the last big punchline. It shipped Thursday.