Skip to content

← Writing

engineering

PHP 8.4: Property Hooks at Last

· 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.4 shipped November 21, and this year’s edition of the annual language notes leads with the feature PHP has been faking since forever: property hooks.

Property hooks: the getter/setter funeral

Twenty years of PHP architecture has been shaped by one missing feature. Because plain properties couldn’t intercept access, every framework and style guide pushed getName()/setName() ceremonies or __get magic — the first verbose, the second invisible to tooling. Hooks end it:

class Invoice
{
    public Money $total {
        get => $this->total->withCurrency($this->currency);
        set (Money $value) {
            if ($value->amount < 0) {
                throw new InvalidArgumentException('No negative invoices');
            }
            $this->total = $value;
        }
    }
}

Callers read and write $invoice->total like a plain property; validation and derivation happen transparently; refactoring a dumb property into a smart one no longer breaks the call sites. That last clause is the actual gift — the entire defensive habit of “wrap everything in getters just in case” loses its reason to exist. Eloquent’s accessors/mutators have simulated this for years framework-side; the language now does it natively, typed, and visible to static analysis.

Asymmetric visibility: the other half

public private(set) Money $total; — readable everywhere, writable only inside the class. Combined with hooks, PHP’s property model goes from “public or ceremonial” to genuinely expressive: read-only-from-outside no longer requires readonly’s all-or-nothing or a getter in a trench coat. Value objects, aggregates, DTOs — the modeling vocabulary that’s been accreting since 8.0 feels complete for the first time.

The crowd-pleaser

$name = new ReflectionClass($class)->getShortName();

new without wrapping parentheses when chaining. A tiny grammar fix for a decade-old irritation — (new Foo())->bar() always read like an apology. Add array_find() / array_any() / array_all() (the trio every codebase reimplemented in a helpers file), lazy objects in core (proxies that defer construction — ORM and DI internals get cleaner), and a real HTML5 parser (Dom\HTMLDocumentDOMDocument’s twenty-year grudge against modern markup, settled), and 8.4 is dense with quality-of-life.

Upgrade posture and the arc

The 8.3 → 8.4 jump is gentle; the usual rhythm applies — framework support lands fast, ecosystem by Q1, the dynamic-properties deprecation from 8.2 keeps inching toward its 9.0 reckoning. Run the suite with deprecations on; budget the familiar afternoon.

Zooming out, because five of these annual posts now make a trend line: 8.0 modernized the syntax, 8.1 brought enums, 8.2 immutability, 8.3 polish, and 8.4 finishes the object model. The language that was a punchline when this blog started is now — quietly, annually, without a single keynote — one of the best-equipped tools in its class for the domain modeling work that pays my invoices. The November release is the most dependable good news in my calendar. Next year’s edition has thirty years of PHP to toast; see you then.