Zum Inhalt springen

PHP 8.4 in Practice: Property Hooks, Asymmetric Visibility and More

Veröffentlicht am Mar 10, 2025 | ca. 1 Min. Lesezeit |
php

PHP 8.4 was released in November 2024 and brings some of the most interesting language features in years. Property hooks and asymmetric visibility fundamentally change how you model objects with properties. This post demonstrates the key new features with practical examples.

Property Hooks

Property hooks are the highlight of PHP 8.4. They allow you to execute custom logic when reading (get) and writing (set) a property — without explicit getter and setter methods.

Basic Syntax

<?php

declare(strict_types=1);

class Temperature
{
    public float $celsius {
        get => $this->celsius;
        set (float $value) {
            if ($value < -273.15) {
                throw new \ValueError('Temperature below absolute zero');
            }
            $this->celsius = $value;
        }
    }

    public function __construct(float $celsius)
    {
        $this->celsius = $celsius;
    }
}

$temp = new Temperature(20.0);
echo $temp->celsius; // 20.0

$temp->celsius = -300; // ValueError: Temperature below absolute zero

Computed Properties

A common use case: properties that are computed from other properties:

<?php

declare(strict_types=1);

class FullName
{
    public string $full {
        get => trim($this->first . ' ' . $this->last);
    }

    public function __construct(
        public string $first,
        public string $last,
    ) {
    }
}

$name = new FullName('Thomas', 'Wunner');
echo $name->full; // "Thomas Wunner"

Property Hooks in Interfaces

Interfaces can define property hooks that implementing classes must fulfill:

<?php

declare(strict_types=1);

interface HasLabel
{
    public string $label { get; }
}

class Product implements HasLabel
{
    public string $label {
        get => strtoupper($this->name);
    }

    public function __construct(
        public readonly string $name,
    ) {
    }
}

Asymmetric Visibility

Asymmetric visibility solves a long-standing problem: properties that should be readable from outside but not (or only restrictedly) writable. Previously, this was only possible via explicit getters.

<?php

declare(strict_types=1);

class Order
{
    // Publicly readable, only internally writable
    public private(set) string $status = 'pending';

    // Publicly readable, writable by subclasses and internally
    public protected(set) \DateTimeImmutable $createdAt;

    public function __construct()
    {
        $this->createdAt = new \DateTimeImmutable();
    }

    public function confirm(): void
    {
        $this->status = 'confirmed'; // Internal: allowed
    }
}

$order = new Order();
echo $order->status; // "pending" — reading ok

$order->status = 'confirmed'; // Error: Cannot modify public(private set) property
$order->confirm(); // Via method: ok

Compared to readonly

readonly allows only a single assignment. private(set) allows any number of assignments, but only internally:

<?php

declare(strict_types=1);

class Counter
{
    public private(set) int $count = 0;

    public function increment(): void
    {
        $this->count++; // Allowed, because internal
    }
}

$counter = new Counter();
$counter->increment();
$counter->increment();
echo $counter->count; // 2 — reading ok
$counter->count = 5; // Error — not writable from outside

New Array Functions

PHP 8.4 adds four new array functions that simplify common operations.

array_find()

Returns the first element that satisfies the callback — analogous to JavaScript's Array.find():

<?php

declare(strict_types=1);

$users = [
    ['name' => 'Alice', 'age' => 30],
    ['name' => 'Bob', 'age' => 25],
    ['name' => 'Charlie', 'age' => 35],
];

$adult = array_find($users, fn(array $u): bool => $u['age'] >= 30);
// ['name' => 'Alice', 'age' => 30]

// Previously you had to write:
$adult = null;
foreach ($users as $user) {
    if ($user['age'] >= 30) {
        $adult = $user;
        break;
    }
}

array_find_key()

Returns the key of the first matching element:

<?php

declare(strict_types=1);

$products = ['apple' => 1.20, 'banana' => 0.50, 'cherry' => 2.00];

$expensiveKey = array_find_key($products, fn(float $price): bool => $price > 1.50);
// 'cherry'

array_any() and array_all()

Check whether at least one element (any) or all elements (all) satisfy a condition:

<?php

declare(strict_types=1);

$scores = [75, 82, 91, 68, 88];

$anyPassed = array_any($scores, fn(int $score): bool => $score >= 90);
// true (91 >= 90)

$allPassed = array_all($scores, fn(int $score): bool => $score >= 60);
// true (all >= 60)

$allExcellent = array_all($scores, fn(int $score): bool => $score >= 90);
// false

#[\Deprecated] Attribute

PHP 8.4 introduces an official #[\Deprecated] attribute that allows you to mark your own functions and methods as deprecated:

<?php

declare(strict_types=1);

class UserService
{
    #[\Deprecated(
        message: 'Use findByEmail() instead',
        since: '2.0',
    )]
    public function getUserByEmail(string $email): ?User
    {
        return $this->findByEmail($email);
    }

    public function findByEmail(string $email): ?User
    {
        // ...
    }
}

When calling a method marked this way, PHP generates an E_USER_DEPRECATED warning — just like with PHP's own deprecated functions.

HTML5 Parser in ext-dom

The DOM extension receives a new, fully HTML5-compliant parser that correctly parses modern HTML documents:

<?php

declare(strict_types=1);

$dom = Dom\HTMLDocument::createFromString('
    <html>
    <body>
        <article>
            <h1>Titel</h1>
            <p>Inhalt</p>
        </article>
    </body>
    </html>
');

$article = $dom->querySelector('article');
$heading = $article->querySelector('h1');
echo $heading->textContent; // "Titel"

This is particularly useful for web scraping and HTML transformation, where the previous DOMDocument often struggled with modern HTML.

Conclusion

PHP 8.4 is a solid release that significantly improves object orientation with property hooks and asymmetric visibility. The new array functions are welcome additions that shorten common patterns. If you are using Symfony 7 or another modern framework, you can adopt PHP 8.4 without hesitation — compatibility is excellent.

As a first step, the installation is straightforward:

# In DDEV .ddev/config.yaml
php_version: "8.4"

ddev restart
ddev exec php --version
Thomas Wunner

Thomas Wunner

Certified IT specialist for application development with an instructor qualification and over 14 years of experience building scalable web applications with Symfony and Shopware. When not coding, Thomas volunteers as a lifeguard with the Wasserwacht, performs as a DJ, and explores the countryside on his motorbike.

Kommentare

Kommentare werden von Remark42 bereitgestellt. Beim Laden werden Daten an unseren Kommentar-Server übertragen.