'payed'. One letter, half a day of debugging, a few years ago. The string walked straight through OrderStatus::isValid(), because someone had added it to the constants list “for compatibility”. Every project I touched has that class: a bag of string constants, an isValid() helper, and a prayer. The type system never knew these strings were special.

PHP 8.1 came out yesterday. Point release on paper, and it brings the feature I wanted since forever. Enums.

enum OrderStatus: string
{
    case New = 'new';
    case Paid = 'paid';
    case Shipped = 'shipped';

    public function isFinal(): bool
    {
        return $this === self::Shipped;
    }
}

public function transition(Order $order, OrderStatus $to): void

A wrong string cannot enter transition() at all, the engine stops it. OrderStatus::from('payed') throws at the boundary, exactly where bad input should die. Behavior lives on the enum itself, so the endless StatusHelper classes can retire. Backed cases handle the database side. This is not sugar. It moves a whole family of bugs from runtime to type check.

Readonly properties are the second gift, aimed at value objects. Public property, written once in the constructor, immutable after. All those private fields with a getter that exists only to protect against mutation, gone. A Money class is now five honest lines.

And Fibers, the loud one. What they are: a low-level way to pause a function and resume it later, cooperative, no scheduler included. Your Laravel app does not become non-blocking by upgrading, and no flag will make it so. Fibers exist so Amp and ReactPHP can hide their event loops behind normal-looking code, without promise chains. Plumbing for library authors. Important plumbing. If you are not writing an event loop, you will meet Fibers only indirectly, some years from now, inside a dependency.

Enums alone are worth the upgrade. Start with the status field everyone is afraid to touch. In our case that is OrderStatus, and the one afraid is me.