Timeline Taxi Out now: my sci-fi novel Timeline Taxi is published!

What's new in PHP 8.4

PHP 8.4 will be released on November 21, 2024. It'll include property hooks, HTML 5 support, as well as chaining methods on new without additional parentheses — a big one!


# Property hooks RFC

One of the biggest changes in modern-PHP history: the ability to define property hooks, eliminating the need for a lot of boilerplate code.

class BookViewModel
{
    public function __construct(
        private array $authors,
    ) {}

    public string $credits {
        get {
            return implode(', ', array_map(
                fn (Author $author) => $author->name, 
                $this->authors,
            ));
        }
    }
    
    public Author $mainAuthor {
        set (Author $mainAuthor) {
            $this->authors[] = $mainAuthor;
            $this->mainAuthor = $mainAuthor;
        }
        
        get => $this->mainAuthor;
    }
}

The goal of property hooks is to remove a lot of getters and setters, by allowing each property to define its own get and set hooks. Hooks are optional, and you don't have to add both of them on a specific property. For example, a property with only a get hook is virtual property.

There is a lot to say about property hooks, and I plan to write a followup post on them soon, so make sure to subscribe if you want to know when that one is done. One final thing I'd like to mention — probably what I'm most hyped about: property hooks can be defined in interfaces!

interface HasAuthors
{
    public string $credits { get; }
    public Author $mainAuthor { get; set; }
}

# new without parentheses RFC

As if property hooks alone wasn't enough, PHP 8.4 has another feature that will save so much boilerplate code: you don't have to wrap new invocations within parenthesis anymore to be able to chain methods on them. So instead of doing this:

$name = (new ReflectionClass($objectOrClass))->getShortName();

You can now do this:

$name = new ReflectionClass($objectOrClass)->getShortName();

I don't know about you, but I write a lot of code like this, and so I'm super happy that we're finally getting rid of those brackets. It doesn't only work for methods, by the way. You can also chain properties, static methods, constants — whatever you want. You can read all about this new feature in this dedicated post.


# Asymmetric visibility RFC

Another ground-breaking feature of PHP 8.4 is asymmetric visibility. Asymmetric visibility allows class properties to define their visibility (public, protected, or private), based on the read or write context. The most common example of asymmetric visibility are public properties that can only be changed from within the class. Such a property would look like this:

class BookViewModel
{
    public private(set) Author $author;
}

Because "public properties that can only be changed within a private context" are the most common use-case for asymmetric visibility, there's also a shorthand available:

class BookViewModel
{
    private(set) Author $author; // same as public private(set)
}

Of course, you can also make properties only writeable within the protected scope:

class BookViewModel
{
    public protected(set) Author $author;
}

And naturally, the syntax works for promoted properties as well:

public function __construct(
    private(set) Author $author;
) {}

# array_find RFC

There's a pretty simple new function added in PHP 8.4, one of those functions that you have to wonder about "hang on, wasn't that available yet?" I guess most developers have grown used to third party collection classes, although I think having array_find() natively in PHP is pretty nice.

The naming might be a bit confusing though, because what this function does is it takes an array and callback, and will return the first element for which the callback returns true:

$firstMatch = array_find(
    $posts, 
    function (Post $post) {
        return strlen($post->title) > 5; 
    }
);

There are also some variations of this function called array_find_key(), array_any() and array_all(), you can read all about them here.


# Implicit nullable types deprecation

PHP had this weird behaviour where a typed variable with a default null value would be made nullable automatically:

function save(Book $book = null) {}

// Deprecated: Implicitly marking parameter $book as nullable is deprecated,
// the explicit nullable type must be used instead

This behaviour is now deprecated and will be removed in PHP 9. The solution is to make Book explicitly nullable:

function save(?Book $book = null) {}

# New HTML5 support RFC

PHP 8.4 adds a \Dom\HTMLDocument class which is able to parse HTML5 code properly. The old \DOMDocument class is still available for backwards compatibility.

$doc = \Dom\HTMLDocument::createFromString($contents);

You can read all about the new HTML 5 parser here.


# JIT changes RFC

PHP 8.4 changes the way the JIT is enabled. Previously, you had to set opcache.jit_buffer_size to 0 in order to disable the JIT, but now you can disable it like so:

opcache.jit=disable
opcache.jit_buffer_size=64m

The only way users can be affected by this change is if they did specify a opcache.jit_buffer_size but no opcache.jit. In that case, you'll have to add opcache.jit=tracing to enable the JIT again.

Finally, there have also been some improvements to the JIT which makes it run faster in some cases, and use less memory.


# Lazy objects RFC

Finally, PHP 8.4 adds native support for lazy objects, a common pattern used by frameworks to build proxy objects with.

$initializer = static function (MyClass $proxy): MyClass {
    return new MyClass(123);
};
 
$reflector = new ReflectionClass(MyClass::class);

$object = $reflector->newLazyProxy($initializer);

# Exit and die as functions RFC

In PHP, exit (and its alias die) are kind of weird things: they can be used as a keyword like so: exit;, but also like a function: exit(0);. The function variant though isn't really a function, rather it is something that kind of behaves like a function, but not entirely:

With PHP 8.4 though, exit() and die() are now properly treated as functions, and all of the above has been fixed. Note that the keyword variant without brackets of course still works the same.


# Object API for BCMath RFC

bcmath used to only support a functional API. With PHP 8.4, you can now use an object-oriented API as well. What's especially cool about this feature is that these Number objects support operator overloading, and thus you can use them directly in calculations!

use BCMath\Number;
 
$num = new Number('1');
$num2 = new Number('2');
$result = $num + $num2;
 
$result->value; // '3'

# The #[Deprecated] attribute RFC

PHP now has a built-in attribute that can be used to mark methods, functions, and classes deprecated. Previously, you could already use a docblock like /** @deprecated */, but PHP itself didn't do anything with it. While static analysers and IDEs were able to interpret these docblocks, you'd need external tooling to make sure all userland deprecations were detected.

Packages and frameworks can now instead rely on PHP's built-in #[Deprecated] attribute, which also supports adding some metadata:

#[Deprecated("use newFunction() instead", since: "tempest/framework:1.1")]
function oldFunction() {
    // …
}

# Smaller additions


# Backwards incompatible changes


Noticed a tpyo? You can submit a PR to fix it. If you want to stay up to date about what's happening on this blog, you can subscribe to my mailing list: send an email to brendt@stitcher.io, and I'll add you to the list.