PHP 8.4 was released on November 21, 2024. It is the most feature-rich minor release since PHP 8.1 introduced enums and fibers. The two headline additions — property hooks and asymmetric visibility — eliminate large classes of boilerplate without changing runtime semantics.
>Property Hooks
Property hooks let you attach get and set logic directly to a property declaration, removing the need for separate getter/setter methods in many cases.
<?php
class User
{
// Computed property — no backing storage, get-only
public string $fullName {
get => "$this->firstName $this->lastName";
}
// Validated setter — modifies the value before storing
public string $email {
set(string $value) {
if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
throw new ValueError("Invalid email: $value");
}
$this->email = strtolower($value);
}
}
// Both hooks — normalise on write, format on read
public string $phone {
get => '+' . ltrim($this->phone, '+');
set => preg_replace('/D/', '', $value);
}
public function __construct(
public readonly string $firstName,
public readonly string $lastName,
string $email,
) {
$this->email = $email;
}
}
$user = new User('Alice', 'Smith', 'Alice@Example.COM');
echo $user->email; // alice@example.com
echo $user->fullName; // Alice SmithNOTEProperties with hooks cannot be used with readonly unless the hook is get-only. Interface properties can now declare required hooks.
>Asymmetric Visibility
Properties can now have different read and write visibility — a pattern previously requiring manual getter methods.
<?php
class Order
{
// Public read, private write
public private(set) int $itemCount = 0;
// Public read, protected write
public protected(set) OrderStatus $status = OrderStatus::Draft;
public function addItem(OrderItem $item): void
{
$this->items[] = $item;
$this->itemCount++; // ✓ private write allowed here
}
protected function updateStatus(OrderStatus $status): void
{
$this->status = $status; // ✓ protected write allowed here
}
}
$order = new Order();
echo $order->itemCount; // ✓ public read
$order->itemCount = 5; // ✗ Error: cannot write private(set) from outside>New array functions
<?php
$users = [
['name' => 'Alice', 'age' => 30, 'active' => true],
['name' => 'Bob', 'age' => 17, 'active' => false],
['name' => 'Carol', 'age' => 25, 'active' => true],
];
// array_find() — first element matching predicate, or null
$firstAdult = array_find($users, fn($u) => $u['age'] >= 18);
// ['name' => 'Alice', ...]
// array_find_key() — key of first matching element, or null
$key = array_find_key($users, fn($u) => $u['name'] === 'Carol');
// 2
// array_any() — true if at least one element matches
$hasInactive = array_any($users, fn($u) => !$u['active']);
// true
// array_all() — true if every element matches
$allAdults = array_all($users, fn($u) => $u['age'] >= 18);
// false>#[\Deprecated] attribute
<?php
class PaymentGateway
{
#[Deprecated(
message: 'Use processPayment() instead.',
since: '2.0',
)]
public function charge(int $cents): bool
{
return $this->processPayment($cents);
}
public function processPayment(int $cents): bool { /* ... */ }
}
// Calling a deprecated method triggers E_USER_DEPRECATED
$gw = new PaymentGateway();
$gw->charge(1000); // Deprecated: charge() — Use processPayment() instead.>new without parentheses
<?php
// ✗ PHP 8.3 — parentheses required to chain
$result = (new Parser())->parse($input);
// ✓ PHP 8.4 — chain directly
$result = new Parser()->parse($input);
// Works with static methods, property access, array access
$value = new Config()->get('key');
$version = new ApiClient()->version;