PHP 8.4 is due in November 2024 and its headline features address two long-standing awkwardnesses. Property hooks allow computed and validated properties without getter/setter boilerplate. Asymmetric visibility lets you expose a property publicly for reading but restrict writing to the class. Chaining on new without parentheses removes a syntactic annoyance. I preview all three with practical examples.
Property Hooks – goodbye getter/setter boilerplate
<?php
declare(strict_types=1);
// Before PHP 8.4 - classic getter/setter DTO
class ProductDto
{
private string $_name = '';
private float $_price = 0.0;
public function getName(): string { return $this->_name; }
public function setName(string $name): void
{
if (empty($name)) throw new \InvalidArgumentException('Name cannot be empty');
$this->_name = trim($name);
}
public function getPrice(): float { return $this->_price; }
public function setPrice(float $price): void
{
if ($price < 0) throw new \InvalidArgumentException('Price cannot be negative');
$this->_price = $price;
}
}
// PHP 8.4 - property hooks eliminate the boilerplate
class ProductDto
{
public string $name {
get => $this->name;
set {
if (empty($value)) throw new \InvalidArgumentException('Name cannot be empty');
$this->name = trim($value);
}
}
public float $price {
get => $this->price;
set {
if ($value < 0) throw new \InvalidArgumentException('Price cannot be negative');
$this->price = $value;
}
}
}
$product = new ProductDto();
$product->name = ' Widget '; // triggers set hook - trims whitespace
echo $product->name; // 'Widget' - get hook returns stored value
$product->price = -5.0; // throws InvalidArgumentException
Computed properties with get hook
<?php
declare(strict_types=1);
class Order
{
public function __construct(
public readonly array $items, // array of ['price' => float, 'qty' => int]
public readonly float $taxRate,
) {}
// Computed property - no stored value, calculated on access
public float $subtotal {
get => array_sum(array_map(fn($i) => $i['price'] * $i['qty'], $this->items));
}
public float $taxAmount {
get => round($this->subtotal * $this->taxRate, 2);
}
public float $grandTotal {
get => $this->subtotal + $this->taxAmount;
}
public int $itemCount {
get => array_sum(array_column($this->items, 'qty'));
}
}
$order = new Order(
items: [
['price' => 9.99, 'qty' => 2],
['price' => 49.99, 'qty' => 1],
],
taxRate: 0.23
);
echo $order->subtotal; // 69.97
echo $order->taxAmount; // 16.09
echo $order->grandTotal; // 86.06
echo $order->itemCount; // 3
Asymmetric Visibility
<?php
declare(strict_types=1);
// PHP 8.4 - public read, private write
class OrderCollection
{
// public get, private set - readable by everyone, writable only by this class
public private(set) int $count = 0;
public private(set) float $totalValue = 0.0;
private array $orders = [];
public function add(\Magento\Sales\Api\Data\OrderInterface $order): void
{
$this->orders[] = $order;
$this->count++; // allowed - private(set) in this class
$this->totalValue += $order->getGrandTotal(); // allowed
}
public function remove(int $index): void
{
if (isset($this->orders[$index])) {
$this->totalValue -= $this->orders[$index]->getGrandTotal();
unset($this->orders[$index]);
$this->count--;
}
}
}
$collection = new OrderCollection();
$collection->add($order1);
$collection->add($order2);
echo $collection->count; // 2 - public read
echo $collection->totalValue; // sum - public read
// $collection->count = 99; // Fatal error: Cannot set private(set) property from outside
Chaining new without parentheses
<?php
// Before PHP 8.4 - parentheses required to chain on new
$slug = (new UrlBuilder())->setBase('example.com')->setPath('/product')->build();
$data = (new Collection())->addItem($a)->addItem($b)->toArray();
// PHP 8.4 - no parentheses needed
$slug = new UrlBuilder()->setBase('example.com')->setPath('/product')->build();
$data = new Collection()->addItem($a)->addItem($b)->toArray();
// Especially useful with builder pattern
$searchCriteria = new SearchCriteriaBuilder()
->addFilter('status', 'active')
->setPageSize(20)
->create();
Other additions in PHP 8.4
<?php
// array_find() graduated from 8.3 (where it was actually introduced)
// Confirmed stable in 8.4
// New HTML parsing functions - finally safe HTML manipulation
$dom = \Dom\HTMLDocument::createFromString('<div class="product"><span>Test</span></div>');
// Uses proper HTML5 parsing (libgumbo) instead of older libxml quirks
// Deprecated in PHP 8.4 (errors in PHP 9.0):
// Implicit nullable parameters (if not already fixed from 8.1)
// function foo(string $x = null) - still needs to be ?string $x = null
Summary
PHP 8.4’s property hooks are the most significant ergonomics improvement since constructor promotion in PHP 8.0. The get/set syntax eliminates getter/setter boilerplate while keeping full validation logic. Asymmetric visibility is the cleaner solution to the “expose read, protect write” pattern that previously required a private property with a public getter. Both features are backward-compatible and can be adopted incrementally in new code while older code continues to work.
