# 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.

- Published: 2020-11-01
- Category: Languages
- Tags: PHP
- Reading time: 3 min read
- Source: https://www.muhammetsafak.com.tr/en/blog/preparing-for-php-8-union-types-match-named-arguments/
- Language: en-US
- Author: Muhammet Şafak

---
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`](/en/blog/php-7-4-typed-properties-and-arrow-functions/) brought the type system down to the property level. In PHP 8.0, function signatures can accept more than one type:

```php
// 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:

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

In PHP 8.0, the PHPDoc is no longer necessary:

```php
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.

```php
// 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:

```php
$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:

```php
// Positional — what's the fourth parameter?
array_slice($array, 0, null, true);

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

This works for functions you write yourself too:

```php
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: 'ahmet@example.com',
    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`.
- Passing too few arguments to a built-in function will now throw an `ArgumentCountError`.
- `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.
