PHP · Course navigation

PHP strict types, match, and the nullsafe operator

Use union types, a match expression, constructor promotion, and ?-> to write safer code.

On this page
Before you start

PHP functions and classes.

  • Use union types, a match expression, constructor promotion, and ?-> to write safer code.
  • Predict the sample output and explain the state changes.
  • Complete the practice task and check the boundary cases.

The concept

declare(strict_types=1) tells PHP not to convert argument types silently. With it, passing an int where a string is required is a TypeError instead of a hidden conversion. Union types such as int|string|null list the types a parameter accepts. The match expression compares with strict equality, returns a value, and throws if nothing matches, unlike switch.

Constructor promotion declares and assigns properties directly in the constructor parameter list. The nullsafe operator ?-> stops and gives null if the object on its left is null, and ?? supplies a default. Together they handle optional data compactly.

Code example

PHP
<?php
declare(strict_types=1);

function describe(int|string|null $value): string
{
    return match (true) {
        $value === null => "nothing",
        is_int($value) && $value < 0 => "negative int",
        is_int($value) => "int $value",
        default => "string '$value'",
    };
}

echo describe(5), "\n", describe(-2), "\n", describe("hi"), "\n", describe(null), "\n";

class Address
{
    public function __construct(public ?string $city = null) {}
}

class User
{
    public function __construct(public string $name, public ?Address $address = null) {}
}

$users = [new User("Ada", new Address("London")), new User("Bo")];
foreach ($users as $user) {
    echo $user->name, ": ", $user->address?->city ?? "unknown", "\n";
}

try {
    echo strlen(123);
} catch (TypeError $e) {
    echo "TypeError caught\n";
}
Output
int 5
negative int
string 'hi'
nothing
Ada: London
Bo: unknown
TypeError caught

Follow the code

Open the walkthrough to inspect the example one step at a time.

Show guided walkthrough
Guided walkthroughReady
1<?php
2declare(strict_types=1);
3
4function describe(int|string|null $value): string
5{
6 return match (true) {
7 $value === null => "nothing",
8 is_int($value) && $value < 0 => "negative int",
9 is_int($value) => "int $value",
10 default => "string '$value'",
11 };
12}
13
14echo describe(5), "\n", describe(-2), "\n", describe("hi"), "\n", describe(null), "\n";
15
16class Address
17{
18 public function __construct(public ?string $city = null) {}
19}
20
21class User
22{
23 public function __construct(public string $name, public ?Address $address = null) {}
24}
25
26$users = [new User("Ada", new Address("London")), new User("Bo")];
27foreach ($users as $user) {
28 echo $user->name, ": ", $user->address?->city ?? "unknown", "\n";
29}
30
31try {
32 echo strlen(123);
33} catch (TypeError $e) {
34 echo "TypeError caught\n";
35}

Follow the example

Start to inspect the source, values, and output. These prepared steps explain the published sample; grouped loop steps describe multiple iterations.

View the complete dry-run table
Prepared trace of the published program. Some rows group repeated iterations.
Step / lineWhat happensState after stepOutput so far
From here on, scalar arguments are not converted silently.—
—
A union type: the function accepts an int, a string, or null.—
—
match (true) tests each condition in order and returns the first arm that matches.—
—
Each call is routed to the right arm.—
int 5
negative int
string 'hi'
nothing
Constructor promotion: the parameters become properties automatically.—
int 5
negative int
string 'hi'
nothing
For Bo, address is null, so ?-> gives null and ?? supplies the default.—
int 5
negative int
string 'hi'
nothing
Ada: London
Bo: unknown
With strict types, passing an int to strlen is a TypeError instead of an automatic conversion.—
int 5
negative int
string 'hi'
nothing
Ada: London
Bo: unknown
The error is a normal exception that can be caught.—
int 5
negative int
string 'hi'
nothing
Ada: London
Bo: unknown
TypeError caught

Behind the scenes

How PHP processes this code

The PHP runtime executes the script. echo writes to the response or terminal output.

Cost and common mistakes

Each call is O(1).

  • match throws UnhandledMatchError if no arm matches and there is no default. Always add default unless the cases are exhaustive.
  • strict_types applies to calls made from the file that declares it. It has to be the first statement in the file.
  • Without strict types, "5 apples" passed to an int parameter is converted with a warning or error depending on the version. Strict types avoid such surprises.

Try it yourself

How does match differ from switch?

Reveal answer and reasoning

match uses strict comparison, returns a value, needs no break statements, and throws an error when no arm matches.

Further reading