Discover the main new features in PHP 8.4

Marcos Marcolin • November 20, 2024 • 5 min read

php php84 phprfc

What's new in PHP 8.4 🐘

As has been tradition over the last decade, PHP ships a new version of the language every year, and now it's PHP 8.4's turn.

If you follow this blog or my LinkedIn, you've probably already seen some of these new features, since I've written posts about them. You can check them out in this topic (in Portuguese).

In this post, I bring an overall look at the main changes. I'll highlight what I consider most relevant, since a lot was done over 1 year of development to make this version even better.

Property Hooks: simplify getters and setters - RFC

Property hooks are a modern approach to defining getters and setters directly in the property declaration inside the class.

class Product 
{
    public float $price {
        set {
            if ($value <= 0) throw new ValueError('Price must be positive!');
            $this->price = $value;
        }
        get {
            return number_format($this->price, 2);
        }
    }

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

This removes the need for repetitive getters and setters, making your code cleaner and easier to maintain.

You can check out my full explanation at this link.

Asymmetric Visibility: more control over properties - RFC

It's now possible to define different scopes for reading and writing properties, allowing public reads while keeping writes restricted, removing the need for redundant getters.

class Product
{
    public private(set) float $price = 19.99;
}

$product = new Product();
var_dump($product->price); // float(19.99)

$product->price = 25.99; // Visibility error

Ideal for scenarios where you need to protect data integrity without giving up public reads.

#[\Deprecated] Attribute - RFC

The new #[\Deprecated] attribute makes PHP's existing deprecation mechanism available for user-defined functions, methods, and class constants.

class Product
{
    #[\Deprecated(
        message: "Use Product::getPrice() instead",
        since: "8.4",
    )]
    public function getProductPrice(): float
    {
        return $this->getPrice();
    }

    public function getPrice(): float
    {
        return 19.99;
    }
}

$product = new Product();
// Deprecated: Method Product::getProductPrice() is deprecated since 8.4, use Product::getPrice() instead
echo $product->getProductPrice();

Full HTML5 support: modernizing the DOM in PHP - RFC / RFC

For over 16 years, HTML5 has been around, but PHP never fully kept up with its evolution. The \DOMDocument class, designed to support HTML4, no longer met modern standards, leaving developers dealing with frustrating limitations.

With PHP 8.4, that gap has finally been filled! A fully HTML5-compatible parser has arrived to modernize working with HTML documents.

New classes Dom\HTMLDocument, Dom\XMLDocument, and methods DOMNode::compareDocumentPosition(), DOMXPath::registerPhpFunctionNS(), DOMXPath::quote(), XSLTProcessor::registerPHPFunctionNS() are now available.

$dom = Dom\HTMLDocument::createFromString(
    <<<HTML
        <section>
            <div class="info">PHP 8.4 introduces powerful new features!</div>
            <div class="destaque">Now you can easily query DOM elements with HTML5 support.</div>
        </section>
        HTML,
    LIBXML_NOERROR,
);

$node = $dom->querySelector('section > div.destaque');
var_dump($node->classList->contains("destaque")); // bool(true)

New array_*() functions - RFC

The new version brings a new set of functions for working with arrays: array_find, array_find_key, array_any, and array_all.

These functions offer a more direct and efficient way to work with arrays, simplifying common operations.

array_find

function array_find(array $array, callable $callback): mixed {}

array_find_key

function array_find_key(array $array, callable $callback): mixed {}

array_all

function array_all(array $array, callable $callback): bool {}

array_any

function array_any(array $array, callable $callback): bool {}

You can check out my full explanation about the new functions at this link.

PDO and SQL: Driver-specific subclasses - RFC

PHP 8.4 introduced new subclasses in PDO, specific to each supported database driver: Pdo\Dblib, Pdo\Firebird, Pdo\MySql, Pdo\Odbc, and Pdo\Sql.

These subclasses bring more clarity and organization, granting access to features specific to each driver, while keeping compatibility with the standard PDO. An improvement that makes working with different databases more intuitive and specialized.

$connection = PDO::connect(
    'sqlite:foo.db',
    $username,
    $password,
); // object(Pdo\Sqlite)

$connection->createFunction(
    'prepend_php',
    static fn ($string) => "PHP {$string}",
);

$connection->query('SELECT prepend_php(version) FROM php'); // PHP 8.4

new MyClass()->method() without parentheses - RFC

It's now possible to directly access methods and properties of a freshly created object without needing to wrap the new expression in extra parentheses.

This makes the code cleaner and more intuitive, especially in direct calls.

class Product
{
    public string $name;

    public function __construct(string $name)
    {
        $this->name = $name;
    }

    public function getFormattedName(): string
    {
        return "Product: {$this->name}";
    }
}

echo new Product('Notebook')->getFormattedName(); // Product: Notebook

You can check out my full explanation about this feature at this link.

Object API for BCMath - RFC

The new BcMath\Number object enables the use of standard math operations in an object-oriented way with arbitrary-precision numbers.

These objects are immutable and implement the Stringable interface, allowing their use in string contexts, such as echo $num.

use BcMath\Number;

$price = new Number('199.99');
$discount = new Number('50.00');
$finalPrice = $price - $discount;

echo $finalPrice; // '149.99'
var_dump($finalPrice > $price); // false

New classes, interfaces, and functions

New JIT implementation based on the IR Framework - RFC

The new JIT implementation based on the IR Framework simplifies the code, improves error detection, makes advanced optimizations easier, and attracts outside experts, reducing complexity and dependence on a small number of developers.

New request_parse_body() function - RFC

The request_parse_body() function allows parsing multipart/form-data in other HTTP methods, such as PUT and PATCH, using PHP's native functionality, currently limited to POST. This avoids manual parsing in your code, improves performance, and makes it easy to reuse POST endpoints.

New bcceil(), bcdivmod(), bcfloor(), and bcround() functions - RFC

New RoundingMode Enum for round() with 4 new rounding modes: TowardsZero, AwayFromZero, NegativeInfinity, and PositiveInfinity - RFC

New http_get_last_response_headers(), http_clear_last_response_headers(), and fpow() functions.

New mb_trim(), mb_ltrim(), mb_rtrim(), mb_ucfirst(), and mb_lcfirst() functions.

Deprecations and backwards-incompatible changes

Implicitly nullable parameter types are now deprecated - RFC

Implicitly nullable types have been deprecated, marking an important step toward greater clarity and consistency in the language.

Now, you must explicitly declare that a type can be null using ?T or T|null.

This change lays the groundwork for fully removing this feature in PHP 9.

// Before (valid, but now raises a deprecation notice):
function foo(string $var = null): void {
    // $var can be null, but the type doesn't explicitly reflect that.
}

// Now (recommended):
function foo(?string $var = null): void {
    // Explicit declaration that $var can be null.
}

// Or
function foo(string|null $var = null): void {
    // Valid alternative using union types.
}

Functions with signatures like function foo(string $var = null) now generate the following compile-time notice in PHP 8.4:

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

Using _ as a class name is now deprecated.

The E_STRICT constant is deprecated.

Behavior change in exit() usage - RFC

In PHP 8.4, exit (and die) was turned from a language construct into a real function with the signature:

function exit(string|int $status = 0): never {}

Future ideas:

Final thoughts

In my opinion, PHP keeps shipping solid versions that meet the community's demands.

The new features added are extremely useful, solving important problems.

Following the language's core is something I really enjoy, even without understanding everything in detail. It's clear that a lot of improvements are being implemented, bringing more performance to the runtime.

I don't even need to get into the old debate about what PHP is or isn't; the language has proven its worth on its own ;)

Be sure to check out EVERYTHING on the official site.

Cheers, and see you next time!

Share: