Articles/PHP/What's New in PHP 8.0

What's New in PHP 8.0

PHP 8.0 was a true major version. Here is a tour of its headline features with practical examples: constructor property promotion, named arguments, the match expression, the nullsafe operator, union types, and string helpers that finally read like English.

November 28, 2020·6 min read

PHP 8.0 landed on 26 November 2020, and unlike the 7.x line it really earned the major version bump. It is the release that brought the JIT compiler, but the changes you actually feel day to day are the language features: less boilerplate, stricter types, and a handful of constructs that make everyday code read better. Let's walk through the ones I reach for most.

Constructor property promotion

For years, declaring a value object meant writing every property three times: once as a field, once as a constructor parameter, once as an assignment. Promotion collapses all three into the parameter list.

PHP
final class Money
{
    public function __construct(
        public readonly int $amountCents,
        public readonly string $currency = 'USD',
    ) {}
}

$price = new Money(1999, 'EUR');
echo $price->amountCents; // 1999

The public readonly in front of each parameter declares the property, sets its visibility, and assigns it for you. A class that used to be fifteen lines is now five, and there is nowhere for a typo between the parameter and the field to hide.

Named arguments

Named arguments let you pass values by parameter name instead of position. They pair beautifully with functions that have a long tail of optional settings, because you can skip straight to the one you care about.

PHP
function createUser(
    string $email,
    bool $active = true,
    bool $verified = false,
    string $role = 'member',
): User {
    // ...
}

$user = createUser('frank@fjp.io', role: 'admin', verified: true);

No more passing true, false, true and counting commas to remember which flag is which. The call site documents itself, and you only name the arguments you are changing from their defaults.

The match expression

match is like switch, but it is an expression that returns a value, it compares with strict ===, and it has no fall-through. That combination kills a whole category of bugs.

PHP
$label = match ($response->status) {
    200, 201, 204 => 'Success',
    301, 302 => 'Redirect',
    404 => 'Not Found',
    500 => 'Server Error',
    default => 'Unknown',
};

Each arm is a single expression, multiple values share an arm with a comma, and an unmatched value with no default throws instead of silently doing nothing. If you have used the spaceship operator or the null coalescing operator, this is the same spirit: small syntax that removes ceremony.

The nullsafe operator

Reaching through a chain of objects that might be null used to mean a ladder of if checks or nested ternaries. The nullsafe operator ?-> short-circuits the whole chain to null the moment any link is missing.

PHP
$country = $user?->getAddress()?->country;

If $user is null, or getAddress() returns null, the expression stops and yields null rather than throwing on a method call against nothing. It reads top to bottom, exactly the way you would describe it out loud.

Union types

PHP 8.0 made union types a native, runtime-checked part of declarations. You can now say a value is one of several types directly in the signature, rather than dropping to a docblock and hoping callers read it.

PHP
function pad(int|string $value, int $width): string
{
    return str_pad((string) $value, $width, '0', STR_PAD_LEFT);
}

pad(7, 4);     // "0007"
pad('42', 4);  // "0042"

This builds on the scalar type hints and return type declarations PHP gained earlier. Where you used to write ?int to mean "int or null" (see nullable types), you can now express far richer combinations, and the engine enforces them.

String helpers that read like English

Checking what a string contains used to mean strpos($haystack, $needle) !== false and remembering that 0 is a valid position. PHP 8.0 finally shipped the three functions everyone had been hand-rolling.

PHP
$file = 'invoice-2020.pdf';

str_contains($file, '2020');   // true
str_starts_with($file, 'inv'); // true
str_ends_with($file, '.pdf');  // true

They return a clean boolean, so no more !== false dance. As a bonus, throw became an expression in 8.0, so you can guard a value inline:

PHP
$config = $options['dsn'] ?? throw new InvalidArgumentException('Missing dsn');

PHP 8.0 set the tone for the entire 8.x line: lean on the type system, write less glue, and let small language features carry the weight that boilerplate used to. Almost everything that follows in 8.1 through 8.5 builds on the foundation laid here.