Skip to content
Muhammet Şafak
tr
Languages 4 min read

Preparing for PHP 8.0: union types, match, and named arguments

PHP 8.0 isn't out yet, but the RFCs and beta releases are already readable. Here are my notes on three key features worth understanding before release.


PHP 8.0 is dropping this month. I’ve been following the beta process, reading the RFCs, and experimenting in a development environment. Before the official release lands, I wanted to write up the three features that most directly affect day-to-day code: union types, the match expression, and named arguments.

This post is framed as a “heads up”: what could the code you write today look like in PHP 8.0, which habits need to change, and what should you be ready for?

Union Types

PHP 7.4’s typed properties brought the type system down to the property level. In PHP 8.0, function signatures can accept more than one type:

// PHP 7.4 — single type
function processInput(string $value): string
{
    return strtoupper($value);
}

// PHP 8.0 — union type
function processInput(int|string $value): int|string
{
    if (is_int($value)) {
        return $value * 2;
    }
    return strtoupper($value);
}

What does this look like in the real world? Today, code like this is common:

/**
 * @param int|string $id
 * @return User|null
 */
public function findUser($id): ?User
{
    // ...
}

In PHP 8.0, the PHPDoc is no longer necessary:

public function findUser(int|string $id): ?User
{
    // ...
}

The ?User syntax (shorthand for User|null) already existed; union types generalize that idea.

One thing worth noting: reaching for a union type isn’t always good design. A function that accepts int|string almost certainly contains two distinct behaviors; splitting that into two separate functions is often the cleaner solution. That said, for genuinely ambiguous input — parsing an API response or an external data source, for example — union types are invaluable.

I’ll also add this: union types deliver the most value when used alongside static analysis tools like PHPStan or Psalm. Your type declaration tells the tool what to expect, and it checks every call site for you. PHPDoc comments could do this too, but type declarations are also enforced at runtime — that difference matters.

The match Expression

switch is one of PHP’s oldest constructs, and it carries a few traps: forgetting break, loose comparison (==), and being a statement rather than an expression. match fixes all of these.

// switch — no strict comparison, break required
$status = 2;
$label = '';
switch ($status) {
    case 1:
        $label = 'Bekliyor';
        break;
    case 2:
        $label = 'İşleniyor';
        break;
    case 3:
        $label = 'Tamamlandı';
        break;
    default:
        $label = 'Bilinmiyor';
}

// match — strict comparison, returns a value, no break
$label = match($status) {
    1       => 'Bekliyor',
    2       => 'İşleniyor',
    3       => 'Tamamlandı',
    default => 'Bilinmiyor',
};

match is an expression — it returns a value and can be used directly in an assignment or a return statement. It uses strict type comparison (===), which eliminates one of the silent mistakes switch used to make.

When multiple conditions map to the same value, you can comma-separate them:

$type = match(true) {
    $score >= 90         => 'A',
    $score >= 75, $score >= 70 => 'B',
    $score >= 60         => 'C',
    default              => 'F',
};

There’s a small but important behavioral difference: if no arm matches and there’s no default, match throws an UnhandledMatchError. With switch, the same situation passes silently and the variable stays empty. match makes that hidden bug visible — which is usually exactly what you want.

Named Arguments

In PHP, you pass arguments to functions positionally. With long parameter lists, it quickly becomes unclear which value maps to which parameter. Named arguments solve this:

// Positional — what's the third parameter?
array_slice($array, 0, true);

// Named — crystal clear
array_slice(array: $array, offset: 0, preserve_keys: true);

This works for functions you write yourself too:

function createUser(
    string $name,
    string $email,
    string $role = 'user',
    bool   $active = true
): User {
    // ...
}

// Especially valuable for optional parameters
$user = createUser(
    name: 'Ahmet Yılmaz',
    email: '[email protected]',
    active: false,  // role skipped, active passed
);

This feature removes the dependency on parameter order. You can now selectively skip over default-valued parameters.

One breaking point I should mention: named arguments make the parameter name part of the contract. If you’ve written a library and you rename a parameter, every call site using named arguments will break. For library authors, this is a significant design decision.

What Should You Do to Prepare?

PHP 8.0 largely preserves backward compatibility, but there are some breaking changes. The most notable:

  • Some comparisons that relied on type looseness will behave differently with match.
  • New warnings for unused function parameters.
  • create_function has been removed.

The fastest way to find breaking points in an existing project is to spin up a development environment and run your tests with php -d error_reporting=E_ALL.


PHP 8.0 is not a minor update. Major changes are coming — including a JIT compiler — but these three features are the ones that touch everyday code writing the most. Reading through the changes now is enough to be ready: once the release drops, the new signatures will feel natural.

Tags: #PHP
Share:

Comments

Sign in with your GitHub account to join the discussion. Comments are stored in GitHub Discussions.

Related Posts

Search the site

Start typing to search posts, projects and pages.

Esc to close Powered by Pagefind