New Features in PHP 8

PHP 8 is almost here!

It will be released on November 26th, 2020 just in time for us Americans to upgrade our servers after Thanksgiving dinner. As it’s a major release there are some breaking changes but also lots of new features and performance improvements. In this article we’ll go over some of the features we’re most looking forward to.

We generally don’t upgrade to a major version until there has been at least 1 patch cycle so wait until PHP hits at least 8.0.1 before installing it on any production servers. However, it’s still a great time to try out the new features in our development environments.

This article is based on PHP 8.0.0RC5.

This article is available as a video on YouTube if you would prefer to listen to it.

Just-In-Time (JIT) Compiler

The most interesting improvement in the PHP 8 release is the inclusion of a Just-In-Time (JIT) Compiler.

Discussing what a JIT compiler does and how it works could be a blog post on its own (and most likely will) but the oversimplified version is this. Before PHP 8, when PHP processed a file PHP converted it from text into a form called bytecode. This bytecode is then passed to the PHP runtime engine which performs the commands in the file. With the JIT enabled the bytecode will get converted to machine code that the underlying processor can use directly.

It’s unclear if this will have actual real-world performance improvements for “normal” CRUD web applications but for some long-running processes we should see performance improvements. We’re excited about this because we want our applications running as quickly as possible to give our users the best possible experience.

Named arguments

It’s common for us to use functions with a lot of arguments. The setcookie() function is an excellent example.

setcookie(
    string $name,
    string $value = "",
    int $expires = 0,
    string $path = "",
    string $domain = "",
    bool $secure = FALSE,
    bool $httponly = FALSE
) : bool {

If we wanted to set a secure cookie we would have to pass all of the optional arguments so we can pass true for the $secure parameter:

setcookie('name', 'value', 0, '', '', true);

This makes the function call hard to read because it has that extra 0, '', '' in it.

Or we might have a function that only has three arguments but determining what each is doing is hard to understand:

currentPosition(0, 1, .55);

PHP 8 allows us to use named arguments to define what each variable we’re passing will be used for.

setcookie(name: "name", value: "value", secure: true);
currentPosition(start: 0, end: 1, percentCompleted: .55);

Named arguments are by far the feature we’re most looking forward to in PHP 8 because it makes our code a lot easier to read at the expense of more typing. This won’t always be needed but it can be helpful in a lot of situations where extra clarity is necessary.

Union Types

We’re a big fan of always defining the types for arguments and return values because it helps us catch mistakes in our code faster because a runtime exception will be raised if there’s a mismatch.

private function aTestFunction(int $param1): int
{
    // snip
}

The downside to this is that we may run into a situation where we might need to pass multiple types or return multiple types. Before PHP 8, we would have to create two versions of the function with different names and parameters.

private function aTestFunction(int $param1): int
{
    // snip
}

private function aTestFunctionWithFloats(float $param1): float
{
    // snip
}

With the newly added union types, we can allow for multiple types to be passed and returned.

private function aTestFunction(int|float $param1): int|float
{
    // snip
}

The nullsafe Operator

In PHP it’s common for us to have a function that might return a class or it might return a null but calling a member function on a null value will cause a runtime error so we have to jump through a hoop like this.

$dateOfEvent = $event->getDate();
$formattedDate = null;
if ($dateOfEvent) {
    $formattedDate = $dateOfEvent->format("m/d/Y");
}

In PHP 8 we can use the newly added nullsafe operator (?->) to make this much shorter.

$formattedDate = $event->getDate()?->format("m/d/Y");

We can also chain these calls together so we can do something like the following.

$dateOrNull = $event->getUser()?->getSubscription()?->getExpiresDate()?->format("m/d/Y);

Match Expression

PHP currently supports the switch statement which allows us a way to write more complicated if/else blocks using the following syntax:

switch ($this->userType) {
    case UserType::ADMIN:
        $name = "Admin";
        break;
    case UserType::LOCAL_ADMIN:
        $name = "Admin";
        break;
    case UserType::STANDARD:
        $name = "User";
        break;
    default:
        $name = "Unknown type";
        break;
}

PHP 8 adds the match expression which allows us to write this more concisely.

$name = match ($this->userType) {
    UserType::ADMIN, UserType::LOCAL_ADMIN => "Admin",
    UserType::STANDARD =>  "User",
    default => "Unknown type",
}

Constructor Property Promotion

This one is a real mouthful but it’s going to be super helpful when creating new classes. Before PHP 8 we had to specify class properties that the construction would receive as parameters and then set them based on what’s passed to the constructor.

class User
{
    public $firstName;
    public $lastName;

    public function __construct(string $firstName, string $lastName)
    {
        $this->firstName = $firstName;
        $this->lastName = $lastName;
    }
}

With constructor property promotion we can define the properties using the constructor’s parameter and set them in one line.

class User
{
    public function __construct(
        public string $firstName,
        public string $lastName
    ) { }
}

It produces a lot less code for the same functionality so it’s a win-win in our department. Now we just need PHP to automatically manage the default getters and setters and we’ll be in great shape.

Trailing Comma in Parameter Lists

When we write functions with a lot of arguments we tend to write one per line so it doesn’t exceed the line length requirements imposed by PSR-12.

function testSomething(
    string $a,
    string $b,
    int $value,
    string message
);

Before PHP 8 the last parameter couldn’t have a trailing command or it would throw an error message. But in PHP 8 it can.

function testSomething(
    string $a,
    string $b,
    int $value,
    string message,
);

On the surface, this may seem like a silly change but it causes additional lines to be changed in our source control systems if we need to add another parameter and it causes the trailing comma to be inconsistent. The change passed almost unanimously https://wiki.php.net/rfc/trailing_comma_in_parameter_list#vote so the core team must see its value for us anal-retentive people.

str_contains() Function

It’s standard practice to use strpos() to determine if a string contains another string.

if (strpos('four score and seven years ago', 'ago') !== false) {
    // do something
}

PHP 8 adds a str_contains() which does the same thing but only returns true or false so we don’t need to add the additional !== false.

if (str_contains('four score and seven years ago', 'ago')) {
    // do something
}

str_starts_with() and str_ends_with() Functions

These two functions have been added to allow users to more quickly determine if a string starts with or ends with another string.

if (str_starts_with('four score and seven years ago', 'four')) {
    // do something
}

if (str_ends_with('four score and seven years ago', 'ago')) {
    // do something
}

Have Fun!

PHP 8 is out soon so we can start to take advantage of these new features in our environment. It’s great to see new features but it’s even better when they make our code easier to understand and maintain.