Skip to content
Tech

What PHP 8.3 and 8.4 Actually Change for Laravel Developers

A practical tour of typed class constants, property hooks, asymmetric visibility and other recent PHP features worth using in Laravel code.

What PHP 8.3 and 8.4 Actually Change for Laravel Developers
What PHP 8.3 and 8.4 Actually Change for Laravel Developers
In this article

    PHP's release cadence has settled into a predictable, useful rhythm: a yearly version with a handful of features that quietly make certain patterns shorter or safer, rather than sweeping syntax changes. 8.3 and 8.4 both fit that pattern. None of this requires rewriting an existing Laravel app, but a few of these are worth reaching for in new code.

    Typed Class Constants (PHP 8.3)

    Class constants could previously hold any type with no enforcement. PHP 8.3 lets you declare the type explicitly, which catches a mismatch at compile time instead of wherever the constant happens to get used.

    class InvoiceStatus
    {
        const string DRAFT = 'draft';
        const string SENT = 'sent';
        const string PAID = 'paid';
    }

    This is a small thing, but it closes a real gap — a constant silently changed from a string to an integer used to be a runtime surprise. Now it's a fatal error at the point of definition.

    The #[Override] Attribute (PHP 8.3)

    A method meant to override a parent's can silently stop doing so if the parent's signature changes or the method name gets a typo during a refactor. #[Override] makes that intent explicit and checkable.

    class OrderController extends Controller
    {
        #[Override]
        public function store(Request $request): JsonResponse
        {
            // ...
        }
    }

    If store() doesn't actually override anything in a parent class — because of a rename upstream, say — PHP raises an error instead of silently defining an unrelated method with the same name.

    json_validate() Without a Round Trip (PHP 8.3)

    if (! json_validate($request->getContent())) {
        abort(400, 'Malformed JSON payload.');
    }

    Before this existed, checking whether a string was valid JSON meant calling json_decode() and checking json_last_error(), which also meant paying the cost of fully decoding a payload you might just want to reject. json_validate() checks syntax without building the resulting structure.

    Property Hooks (PHP 8.4)

    Property hooks let a property run code on read or write without the ceremony of a separate getter and setter pair, while still looking like a plain public property to calling code.

    class Temperature
    {
        public function __construct(public float $celsius)
        {
        }
    
        public float $fahrenheit {
            get => ($this->celsius * 9 / 5) + 32;
            set => $this->celsius = ($value - 32) * 5 / 9;
        }
    }

    Code that uses this class just writes $temp->fahrenheit = 98.6; — no setter method call, no awareness that a conversion happened underneath. This is most useful for value objects and DTOs where a derived value needs to stay in sync with a stored one.

    Asymmetric Visibility (PHP 8.4)

    Previously, a property was either fully public or fully private/protected for both reading and writing. Asymmetric visibility lets you expose a property for reading everywhere while restricting who can write to it.

    class Order
    {
        public private(set) string $status = 'pending';
    
        public function markShipped(): void
        {
            $this->status = 'shipped';
        }
    }

    Anything outside the class can read $order->status freely, but only code inside Order itself can change it — no more defensive readonly plus a separate mutator method just to get the same effect, and no risk of a stray $order->status = 'whatever' from unrelated code.

    Tip: Asymmetric visibility is a good fit for anything resembling a state machine on a model — status fields, workflow stages — where you want the state readable everywhere but changeable only through a method that enforces valid transitions.

    New Array Functions (PHP 8.4)

    $firstOverdue = array_find($invoices, fn ($invoice) => $invoice->is_overdue);
    $anyOverdue = array_any($invoices, fn ($invoice) => $invoice->is_overdue);
    $allPaid = array_all($invoices, fn ($invoice) => $invoice->status === 'paid');

    These replace the common array_filter(...)[0] ?? null idiom for "find the first match" with something that reads as exactly what it does, and stops scanning as soon as it finds an answer rather than filtering the whole array first.

    Adopting These Without Drama

    • Check your deployment target's PHP version before reaching for any of this — Laravel Forge, Vapor and most managed hosts move fast, but shared hosting often lags well behind.
    • Typed constants and #[Override] are safe to adopt incrementally in new code without touching anything existing.
    • Property hooks and asymmetric visibility are worth it for new value objects and models with clear state rules, not worth a retrofit across an entire codebase just because they're available.

    None of these features change how Laravel itself works, but they do change what idiomatic PHP looks like underneath it. Writing new code with them doesn't require migrating anything old — just knowing they're there the next time you reach for a getter/setter pair or a defensive array lookup.

    Written by Rajasekar

    Founder & Editor

    I write HorizonDesk: practical, no-hype articles on technology, money, business and the vehicles we drive. Everything here is something I’ve researched, tested or wanted explained clearly myself.