What’s New and Exciting in PHP 8 4
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, PHPEOL;
“`
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
- PHP 8.4 is set for release on November 21, 2024
- Major changes include Property Hooks and new array functions
Leave a comment
Use the form below to leave a comment: