Every PHP release ships a mix of headline features, quality-of-life tweaks and things you will never touch. PHP 8.4 (November 2024) and PHP 8.5 (November 2025) are unusually rich in the first two categories. This post skips the changelog recitation and focuses on the features that earn a place in day-to-day application code, with examples and the caveats release notes leave out.

Property hooks: delete your getters

Property hooks are the biggest change to PHP's object model in years. A property can now define get and set logic inline, so computed and validated properties no longer need method pairs:

class User
{
    public string $email {
        set (string $value) {
            if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
                throw new InvalidArgumentException('Invalid email');
            }
            $this->email = strtolower($value);
        }
    }

    public string $displayName {
        get => $this->firstName . ' ' . $this->lastName;
    }
}

The practical win is API stability: consumers read $user->email whether it is a plain property today or a hooked one tomorrow. Adopt hooks for validation and lightweight derivation. Avoid hiding expensive work such as database calls behind a get hook; property access that secretly performs I/O is a debugging trap.

Asymmetric visibility: readable outside, writable inside

Asymmetric visibility solves a pattern every codebase contains: state the world may read but only the class may change. Before 8.4 you chose between a private property with a getter or a public property with no protection. Now you write it directly:

class BankAccount
{
    public private(set) int $balanceInCents = 0;

    public function deposit(int $cents): void
    {
        $this->balanceInCents += $cents;
    }
}

Combined with readonly and hooks, this makes most getter methods obsolete. Entities, aggregates and DTOs become dramatically shorter without losing encapsulation.

Array searching that reads like intent

PHP 8.4 added four small functions that remove a persistent annoyance: array_find(), array_find_key(), array_any() and array_all(). Each takes an array and a callback, and each replaces a clunky array_filter-plus-reset dance or a hand-rolled loop:

$firstAdmin = array_find($users, fn($u) => $u->isAdmin());

$hasOverdue = array_any($invoices, fn($i) => $i->isOverdue());

$allPaid = array_all($invoices, fn($i) => $i->isPaid());

PHP 8.5 continues the theme with array_first() and array_last(), ending the era of reset() and end() mutating your array pointer just to peek at an element. These are trivial functions, but they show up in code review constantly, so adopting them pays off immediately.

Small 8.4 wins you get for free

  • new without parentheses: new Money(100)->format() now works; no wrapping parentheses required when chaining directly off instantiation.
  • Lazy objects: native support for proxies and ghost objects means dependency-injection containers and ORMs can defer expensive initialisation without generated proxy classes.
  • HTML5 DOM parsing: the new Dom\HTMLDocument class finally parses real-world HTML correctly, replacing workarounds around the old libxml2-based parser.
  • Deprecated implicit nullable parameters: function f(string $s = null) now warns; write ?string $s = null explicitly.

PHP 8.5: the pipe operator changes how code reads

The headline of PHP 8.5 is the pipe operator |>, which passes the left-hand value as the argument to the callable on the right. Nested transformation code that read inside-out now reads top-to-bottom:

$slug = $title
    |> trim(...)
    |> strtolower(...)
    |> (fn($s) => preg_replace('/[^a-z0-9]+/', '-', $s))
    |> (fn($s) => trim($s, '-'));

Use it where you genuinely have a pipeline of single-argument transformations. Resist contorting multi-argument calls into closures just to keep a chain going; at that point a named private method is clearer.

Beyond the pipe, 8.5 brings clone with syntax for updating readonly objects, the #[\NoDiscard] attribute that warns when a meaningful return value is ignored, fatal error backtraces enabled by default, and persistent cURL share handles that let connection pools survive across FPM requests, a quiet but real performance win for API-heavy applications.

How to adopt without churn

New syntax invites drive-by rewrites that bloat diffs. A calmer strategy works better:

  1. Upgrade the runtime first and run your test suite; both releases have short deprecation lists but real ones.
  2. Adopt new features in new code immediately, guided by your static analysis level.
  3. Use Rector rules to mechanically migrate old patterns (getter pairs to hooks, reset() to array_first()) when a file is already being touched.
  4. Update your coding standard so reviews enforce the new idioms consistently.

Closing thoughts

PHP 8.4 and 8.5 are pragmatic releases: less ceremony around object state, clearer collection code, and a pipe operator that rewards functional composition without forcing it. None of these features demands a rewrite, and all of them make the next file you write a little shorter and a little more honest. That is exactly what mature language evolution should look like.

Related Service

πŸ’» Web Development

Custom websites and web applications built with PHP, Laravel, WordPress, and React β€” fast, secure, scalable, and tailored to your business goals.

Explore Web Development →