php[architect] logo

Want to check out an issue? Sign up to receive a special offer.

What’s New and Exciting in PHP 8 4

Posted by on August 20, 2024

At the time of this writing, PHP 8.4 is in the beta release cycle so we can finally start to discuss what’s new in the next release of PHP. In this article, we’ll discuss the timeline for the release and discuss some new features and changes we can expect to see.

Overall

At the time of this writing, PHP 8.4 is scheduled to be released on November 21, 2024, after it’s gone through alpha, beta, and release candidate phases. It’s always possible the date may change as we would rather have them release working software than something with a bug in it. This happened with the 8.2 release when a critical bug was found just before the release.

What’s included in this release will make it easier for us to develop and maintain our code such as property hooks. Because this is a point release, it’s not expected to be a painful upgrade, but plan accordingly, as this isn’t a given.

I have two disclaimers before we go any further. This article was made using beta release 3 on the official Docker image but functionality may change between now and the actual release. I also can not stress enough not to install this in production yet. Sometime after the release candidates, I’ll start including it in my automated tests so we can see if anything breaks, but I’ll wait until the first patch (which will be 8.4.1 in this case) before we start installing it in ANY production environments.

If you’re interested in trying it now, there are Docker images available and instructions for compiling it directly from the source.

Let’s talk about some of the things that have been added.

Property Hooks

One of the most widely discussed features coming to PHP 8.4 is Property Hooks. Property hooks allow us to add behavior in a way that is specific to a single property while allowing that behavior to not interfere with existing aspects of PHP.

In practice, we can use property hooks by defining a set and get operation that will be performed while setting or getting a property.

The classic way to get around this before PHP 8.4 was to use either a getter/setter pair or logic inside the get()/set() Magic Methods. But these had downsides.

With a setter and getter, you had to maintain both a setter and a getter, and with the Magic Methods, it would break static analysis tools.

I have two favorite examples of how property hooks can be used.

The first is that we might have a user class where we’re tracking both a first and last name but we have occasions where we want to get and set the full name. Note: I took this from the RFC and modified it a little as it’s a great example!

“`
class User
{
public function __construct(public string $first, public string $last) {}

public string $fullName {
    // Override the "read" action with arbitrary logic.
    get => "{$this->first} {$this->last}";

    // Override the "write" action with arbitrary logic.
    set { 
        [$this->first, $this->last] = explode(" ", $value, 2);
    }
}

}

$user = new User(“Scott”, “Keck-Warren”);
// output:
// Scott Keck-Warren
echo $user->fullName, PHP_EOL;

$user->fullName = “Joe Keck-Warren”;
// output:
// Joe
// Keck-Warren
echo $user->first, PHPEOL;
echo $user->last, PHP
EOL;
“`

As you can see from the example we replace the trailing semicolon of the property with a code block ({}) that we then put the property hook into.

We’re not required to define both the set and get and by excluding one or the other we can make the property read-only or write-only.

“`
class User
{
public function __construct(public string $first, public string $last) {}

public string $fullName {
    // Override the "read" action with arbitrary logic.
    get => "{$this->first} {$this->last}";
}

}

$user = new User(“Scott”, “Keck-Warren”);
// output: Uncaught Error: Property User::$fullName is read-only
$user->fullName = “Joe Keck-Warren”;
“`

“`
class User
{
public function __construct(public string $first, public string $last) {}

public string $fullName {
    // Override the "write" action with arbitrary logic.
    set { 
        [$this->first, $this->last] = explode(" ", $value, 2);
    }
}

}

$user = new User(“Scott”, “Keck-Warren”);
// output:
// Property User::$fullName is write-only
echo $user->fullName, PHP_EOL;
“`

I’m not 100% sure but I think this is the only way to create a write-only property.

The other logic that this will be extremely helpful for is validating that the value is “correct”.

“`
class User
{
public function __construct(public string $first, public string $last) {}

public int $stateId = 0 {
    // Override the "write" action with arbitrary logic.
    set { 
        if ($value < 0) {
            throw new Exception("stateId must be greater than 0");
        }

        $this->stateId = $value;
    }
}

}

$user = new User(“Scott”, “Keck-Warren”);
// output:
// Uncaught Exception: stateId must be greater than 0
$user->stateId = -1;
“`

This is a trivial example but it’s going to be perfect for Finite-State Machines because we can validate the transition is valid before we allow it to occur.

Array Functions

PHP 8.4 is also adding new functions that will make it easier for us to search our arrays without having to write a lot of boilerplate code.

array_find

The first function we’re going to discuss is the array_find function which returns the value of the first element for which a custom callback function returns true. If no match is found then the function will return null.

“`
$names = [
“Scott”,
“Samuel”,
“Sarah”,
“Shanon”,
];

// output:
// Samuel
echo array_find(
array: $names,
callback: fn (string $name) => strlen($name) == 6
);
“`

arrayfindkey

The array_find_key function returns the key of the first element for which a custom callback function returns true. If no match is found then the function will return null.

“`
$names = [
“Scott”,
“Samuel”,
“Sarah”,
“Shanon”,
];

// output:
// 1
echo arrayfindkey(
array: $names,
callback: fn (string $name) => strlen($name) == 6
);
“`

array_any

The array_any function returns the true if any element for which a custom callback function returns true. If no match is found then the function will return false.

“`
$names = [
“Scott”,
“Samuel”,
“Sarah”,
“Shanon”,
];

// output:
// true
echo array_any(
array: $names,
callback: fn (string $name) => strlen($name) == 6
) ? “true” : “false”;

// output:
// false
echo array_any(
array: $names,
callback: fn (string $name) => strlen($name) == 60
) ? “true” : “false”;
“`

array_all

Finally, the array_all function returns the true if ALL elements for a custom callback function returns true. If any element causes the callback to return false it will return false.

“`
$names = [
“Scott”,
“Samuel”,
“Sarah”,
“Shanon”,
];

// output:
// true
echo array_all(
array: $names,
callback: fn (string $name) => strlen($name) <= 6
) ? “true” : “false”;

// output:
// false
echo array_all(
array: $names,
callback: fn (string $name) => strlen($name) == 6
) ? “true” : “false”;
“`

new MyClass()->method() without parentheses

Before PHP 8.4 if you wanted to create an instance of a class and then call a method on the newly instantiated class you could do so by wrapping the “new” in parentheses.


$value = (new User("Scott", "Keck-Warren"))
->fullName;

PHP 8.4 changes this so we can exclude the parentheses which should make it a lot easier to read and write.


echo new User("Scott", "Keck-Warren")
->fullName;

One of the more interesting parts of the RFC was the fact that because of this change “more than half a million lines of open source PHP code could be simplified” just in open-source projects. I’ll be interested to see what affect it actually has.

#[\Deprecated] Attribute

Next up a new attribute is being added to allow us to mark functions, class constants, and enum cases as deprecated. PHP internal functions and constants can be marked as deprecated but the #[\Deprecated] attribute will now allow us to mark deprecated code and it will be treated as such.

“`
}

// output:
// Deprecated: Function test() is deprecated in …
test();
“`

This was functionality we could replicate before 8.4 using trigger_error() but it will be so much better having the functionality easily accessible (especially because I could never remember what “level” of error I needed to pass).

Add 4 new rounding modes to round() function

Also new in PHP 8.4 are four new rounding modes to the round() function. If you’re anything like me you didn’t even know that round() allowed you to specify how it should do the rounding.

The new modes allow us to round to the nearest integer bigger than the number (PHPROUNDCEILING), round to the nearest integer lower than the number (PHPROUNDFLOOR), always round away from zero (PHPROUNDAWAYFROMZERO), and always round towards zero (PHPROUNDTOWARD_ZERO).

These additions will be helpful for anyone doing math.

Others

Other things that were changed that I would like to briefly mention are:

ext-dom Improvements

PHP 8.4 brings improvements to the ext-dom extension including HTML 5 support. We may have to do an article on the ext-dom extension at some point as it can be helpful to anyone trying to parse data from a website.

Add http(get|clear)lastresponseheaders() function

Two functions were added to make it easier to work with HTTP responses. These functions are the http_get_last_response_headers() and http_clear_last_response_headers() which allow us to get and clear the errors.

This is being done so the $http_response_header variable can be removed from PHP so if you’re using $http_response_header now would be the time to start cleaning that up.

Multibyte additions

Multibyte functions have been added to handle case and trim logic that’s currently not supported.


<?php
// output:
// string(5) "Scott"
var_dump(mb_ucfirst("scott"));
// output:
// string(5) "sCOTT"
var_dump(mb_lcfirst("SCOTT"));
// output:
// string(5) "Scott"
var_dump(mb_trim(" Scott "));
// output:
// string(9) "Scott "
var_dump(mb_ltrim(" Scott "));
// output:
// string(8) " Scott"
var_dump(mb_rtrim(" Scott "));

Unbundle ext/imap, ext/pspell, ext/oci8, and ext/PDO_OCI

In 8.4 the ext/imap, ext/pspell, ext/oci8, and ext/PDO_OCI extensions will no longer be part of the PHP source distribution and will instead be moved to PECL.

Increasing the default BCrypt cost

The default cost for BCrypt in password_hash() has changed to cope with the fact that computers continue to get faster and faster. If you’re using password_hash() with the default parameters you shouldn’t have to change anything but you should make sure you’re also using password_needs_rehash() to see if the current password needs to be rehashed as this will only be helpful if you’re using both.

Deprecations

There were also some parts of the language that were deprecated and will be removed in PHP 9. Hopefully, Rector will be able to replace those for us automatically.

Deprecate implicitly nullable parameter types

The one that I think is going to have the largest impact on most developers is the change to how PHP automatically creates implicit nullable types.

Currently, we can create a parameter to a function that can be a type or null and specify that the default value is null.


function test(User $user = null) {
//
}

Behind the scenes, PHP was actually converting this to something more like the following:


function test(?User $user = null) {
//
}

It’s a minor difference but it’s more “correct”.

This change in PHP 8.4 will deprecate the functionality that allows this to happen with the idea it will be removed in PHP 9.0. You’ll get a message that says “Deprecated: test(): Implicitly marking parameter $user as nullable is deprecated, the explicit nullable type must be used instead”

What You Need To Know

  1. PHP 8.4 is set for release on November 21, 2024
  2. Major changes include Property Hooks and new array functions

Tags: , ,
 

Leave a comment

Use the form below to leave a comment: