Take Advantage of the New PHP 8/8.1 Features

3/18/2022

If you recently upgraded from PHP 7 to 8, or consider to do so, here’s a quick guide about what to keep in mind and what code you might be able to improve using PHP 8.1’s new features.

Incompatibilities and Depreciations

Of course, the most important thing is that your app continues to run smoothly when upgrading. Although most code from PHP7 is compatible to PHP8 (and 8.1), there are a few things that needs to be updated as it might break otherwise.

For example many libraries/projects using an ArrayAccess implementation stopped working with PHP 8.1, as a previous warning became an error with the new PHP release.

At first you should check all your project’s dependencies for available upgrades. If a dependency is not yet compatible with PHP 8.1 you might need to either look for an alternative, fork and adjust that dependency, or test whether it is compatible yourself.

You can also check if your project had depreciation warnings in the previous version of PHP. These can be an indicator that it might stop working in a later release and should be solved when upgrading.

In your local development environment you can try to run your app (and unit tests if available) using the new PHP version and check if everything is working. If possible, adjust the PHP version in your editor’s settings to 8.1, as it might also show problems/incompatibilities that came up. If you’re using VSCode and php-intellephense, you can do so by searching for php version in the settings (CMD + ,) and entering the (semver) version of your local PHP installation (e.g. 8.1.2).

New Features

Let’s get to the fun part! Of course, PHP8 and 8.1 come with some cool new features that will make your code even better. There are things like enums, named arguments, (string key) array unpacking, union types, nullsafe operators and more that many of us waited for for a long time. Although I won’t consider every new feature in this post, here are my favorites and some examples how to take advantage of them.

Class Properties in Constructors

Starting with PHP 8.0 we can specify class properties right in the constructor, making the code of classes with many properties much cleaner.

Previously:

class User {
    protected int $id;
    protected string $email;
    protected string $firstName;
    protected string $lastName;

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

Now we can simplify it as follows (does exactly the same)

class User {
    public function __construct(
        protected int $id,
        protected string $email,
        protected string $firstName,
        protected string $lastName,
    ) {}
}

Named Arguments

Since PHP 8.0 it’s possible to use named arguments for function calls. This does not only make the code much better readable. We can also leave out arguments with default values and change the order. Here’s a comparison between PHP 7.4 and PHP 8.0:

This is our function:

function test(int $a = 0, string $b = 'foo', string $c = 'bar') {}

Now we want to call that function, but only use a custom value for variable $c.

In PHP7.4 we had to do:

test(0, 'foo', 'baz');

But now we can reduce that to:

test(c: 'baz');

// or flip the order
test(c: 'baz', a: 42);

Enums

Prior to PHP 8.1 there were no enums at all available in PHP. There were a few workarounds but nothing really nice. Most people (including me) just used class constants for that purpose. However, now it’s finally possible.

enum PaymentStatus {
    case Outstanding;
    case Processing;
    case Completed;
    case Failed;
}

class Payment {
    public function __construct(
        protected float $amount,
        protected PaymentStatus $paymentStatus = PaymentStatus::Outstanding,
    ) {}
}

Array Unpacking

Now it’s possible to use array unpacking for string-keyed arrays. Since 7.4 it was possible only with numeric arrays.

$array1 = ['a' => 'lorem', 'b' => 'ipsum'];
$merged = [...$array1, 'c' => 'dolor'];

Union Types

In some cases it might be required to have multiple possible types for an argument. With PHP8 it’s possible to combine them using the | operator:

function whatType(int|string|bool $var): string {
    return match (true) {
        is_int($var) => "It's an integer!",
        is_bool($var) => "It's a boolean!",
        is_string($var) => "It's a string!",
    };
}

Did you notice the match operator? That's next.

Match

The new match expression is very useful to replace some annoying switch statements:

$locale = 'de';
$localeString;

switch ($locale) {
    case 'en':
        $localeString = 'English';
        break;

    case 'de':
        $localeString = 'German';
        break;

    case 'es':
        $localeString = 'Spanish';
        break;

    default:
        throw new Exception('unknown locale!');
};

The same using match:

$locale = 'de';
$localeString = match ($locale) {
    'en' => 'English',
    'de' => 'German',
    'es' => 'Spanish',
    default => throw new Exception('unknown locale!'),
};

Note that match throws an exception if the value does not match and no default return value is given. You can also throw an exception there like in the code example above. Moreover it's possible to combine cases; 'de', 'de_DE', 'de-DE' => 'German‘

You can also use true and custom conditions in each match branch (like in the union types example above).

Nullsafe Operator

The nullsafe operator comes handy when working with methods with nullable return values. The case I used this the most was optional datetime properties:

$example->getDate(); // This method returns either a DateTime object or null

// PHP 7 way:

echo $example->getDate() ? $example->getDate()->format('Y-m-d') : 'no date';
echo $example->getDate()->format('Y-m-d'); // This would fail if getDate() returns null

// PHP 8 way:

echo $example->getDate()?->format('Y-m-d') ?? 'no date';

When getDate() is null, the remaining expression is ignored and the result becomes null.

str_contains, str_starts_with and str_ends_with

strpos('some string', 'string') !== false

becomes

str_contains('some string', 'string')

str_starts_with and str_ends_with can also be useful instead of annoying strpos checks.

Conclusion

These are just my personal favorites of the new features and changes. You can find an overview of all changes on the PHP’s website for PHP 8.0 and PHP 8.1.