What's New in PHP 8.4
PHP 8.4 brought one of the biggest syntax additions of the 8.x line. Here are its headline features with practical examples: property hooks, asymmetric visibility, new without parentheses, the array_find family, the Deprecated attribute, and a modern HTML5 DOM parser.
PHP 8.4 landed on 21 November 2024, and it carries the most significant syntax change since enums: property hooks. Alongside it came asymmetric visibility, a cleaner way to chain off new, some array functions people had been requesting for a decade, and a brand-new HTML5 DOM parser. This is a big one.
Property hooks
Property hooks let a property compute its value on read or transform it on write, without you writing a separate getter and setter method. The property stays a property from the caller's point of view, but you control what happens behind it.
final class Temperature
{
public function __construct(public float $celsius = 0.0) {}
public float $fahrenheit {
get => $this->celsius * 9 / 5 + 32;
set (float $value) => $this->celsius = ($value - 32) * 5 / 9;
}
}
$t = new Temperature(25);
echo $t->fahrenheit; // 77
$t->fahrenheit = 212;
echo $t->celsius; // 100
$fahrenheit stores nothing of its own. Reading it derives a value from celsius, and writing it updates celsius in turn. Callers just use $t->fahrenheit like any other property, with none of the getter/setter ceremony, and the hook is a natural home for validation or normalization too.
Asymmetric visibility
Sometimes you want a property the whole world can read but only the class itself can change. Before 8.4 that meant a private property plus a public getter. Now you can say it in one declaration.
final class ShoppingCart
{
public private(set) array $items = [];
public function add(Product $product): void
{
$this->items[] = $product;
}
}
$cart = new ShoppingCart();
echo count($cart->items); // reading is fine
$cart->items = []; // Error: cannot modify private(set) property
public private(set) means public to read, private to write. Outside code can inspect the cart's items but cannot reach in and replace them, so the only way to change the contents is through add(). I used this exact pattern in SOLID Principles in Modern PHP to model a registry.
new without parentheses
A small but delightful change: you can now call a method or access a property directly on a freshly constructed object without wrapping the new expression in parentheses.
// Before 8.4
$name = (new ReflectionClass($model))->getShortName();
// PHP 8.4
$name = new ReflectionClass($model)->getShortName();
The extra parentheses were pure noise, and now they are gone. It reads exactly the way you would say it: make one of these, then call this on it.
The array_find family
PHP finally shipped the higher-order array functions that every other language has had forever. array_find returns the first matching element, while array_any and array_all answer boolean questions, all without writing a loop and a flag variable.
$users = $repository->all();
$firstAdmin = array_find($users, fn (User $u) => $u->role === 'admin');
$hasAdmin = array_any($users, fn (User $u) => $u->role === 'admin');
$allActive = array_all($users, fn (User $u) => $u->isActive);
array_find returns the matching user or null, so it pairs naturally with the nullsafe operator. There is also array_find_key when you want the key instead of the value. Together they replace a surprising amount of boilerplate loop code.
The Deprecated attribute
You can now mark your own functions, methods, and constants as deprecated with #[Deprecated], and PHP will emit a deprecation notice whenever they are used. Until 8.4 this was only possible for internal PHP functions.
final class PaymentGateway
{
#[Deprecated(message: 'use charge() instead', since: '2.5.0')]
public function makePayment(int $cents): void
{
$this->charge($cents);
}
public function charge(int $cents): void { /* ... */ }
}
Anyone calling makePayment() gets a deprecation warning pointing them at charge(), and static analysis tools surface it too. It is a clean way to guide consumers of your library off an old API without breaking them overnight.
A modern HTML5 DOM
The old DOM extension predated HTML5 and mangled modern markup. PHP 8.4 added Dom\HTMLDocument, a spec-compliant parser with the querySelector API you already know from the browser.
$dom = Dom\HTMLDocument::createFromString(
'<article><h1 class="title">Hello</h1><p>World</p></article>'
);
$title = $dom->querySelector('.title');
echo $title->textContent; // "Hello"
No more wrestling with DOMDocument::loadHTML() and its libxml warnings on perfectly valid HTML5. Scraping, transforming, or testing markup is now genuinely pleasant, with CSS selectors instead of XPath gymnastics.
PHP 8.4 is a landmark release. Property hooks and asymmetric visibility change how you design classes, and the smaller additions sand down chores you have lived with for years. The final stop in this series, 8.5, leans into composition with the pipe operator and a much nicer way to clone objects.