PHPNullsafe Operator

Nullsafe Operator

Reading a chain of properties or method calls — $order->customer->address->city — is convenient right up until one link in that chain might be null. In real applications, that happens constantly: an order might not have a customer yet, a customer might not have an address on file. Before PHP 8.0, safely reading a deep chain like that meant either wrapping it in a pile of isset() calls or writing a nested sequence of if statements checking each link before touching the next one. PHP 8.0's nullsafe operator, ?->, replaces all of that with a single, readable expression.

The old way: nested null checks

To safely read $order->customer->address->city without risking a fatal error on a null property, the pre-8.0 approach looked something like this.

Verbose null-checking before PHP 8.0

PHP
<?php
if ($order !== null) {
    $customer = $order->customer;

    if ($customer !== null) {
        $address = $customer->address;

        if ($address !== null) {
            $city = $address->city;
        }
    }
}

$city ??= 'Unknown';
echo $city;

This works, but the actual logic — "get the city, or fall back to Unknown" — is buried under three levels of nested if statements that exist purely for null-safety, not for anything the reader actually cares about.

The nullsafe operator

?-> replaces -> at any point in a chain. If the value on the left of ?-> is null, PHP stops evaluating immediately and the entire expression evaluates to null — none of the rest of the chain is touched, so no fatal error can occur from trying to read a property off of null.

The same logic with the nullsafe operator

PHP
<?php
$city = $order?->customer?->address?->city ?? 'Unknown';

echo $city;
Unknown

One line replaces the entire nested block above, and it reads as close to plain English as PHP syntax gets: "the order's customer's address's city, if all of those exist, otherwise null" — then the ?? operator supplies the fallback.

Short-circuiting the whole chain

The important behavior to understand is that a single null link short-circuits everything after it — PHP does not attempt to read further and does not throw a warning or error. This holds even when a method call is involved, not just property access.

A null anywhere in the chain stops evaluation

PHP
<?php
class Address {
    public function __construct(public string $city) {}
}

class Customer {
    public ?Address $address = null;
}

class Order {
    public ?Customer $customer = null;
}

$order = new Order();

// $order->customer is null, so PHP never attempts to read ->address
$city = $order->customer?->address?->city;

var_dump($city);
NULL

Notice that only the first ?-> in that chain was strictly necessary to prevent an error, but every link after a nullable one should also use ?->, since any of them could be the one that's actually null depending on the data.

Nullsafe method calls

?-> works identically for method calls — if the object is null, the method is never invoked, and the whole expression is null.

Nullsafe method calls

PHP
<?php
class Logger {
    public function warn(string $message): void {
        echo "WARN: {$message}\n";
    }
}

class Service {
    public ?Logger $logger = null;
}

$service = new Service();

// No error, even though $service->logger is null — warn() is simply never called
$service->logger?->warn('Something happened');

echo "Done\n";
Done
Nullsafe operator vs. isset()

isset() and ?-> solve related but different problems. isset() checks whether a variable or array key exists and is non-null, and it's still the right tool for arrays and for checking whether a variable itself is defined. ?-> specifically short-circuits an object property/method access chain and produces a usable value (typically combined with ??) in one expression, rather than just a boolean.

?-> only short-circuits reads, not assignment targets
The nullsafe operator is for reading values safely — it cannot be used on the left-hand side of an assignment. Writing something like `$order?->customer->name = 'Amara';` is not valid PHP, because "assign a value to a property of something that might not exist" isn't a coherent operation: there's no object to assign to if `$order` is `null`. If you need to conditionally write a value, you still need an explicit `null` check before the assignment.
  • ?-> replaces -> and short-circuits the entire remaining chain the moment any link evaluates to null.

  • No warning or fatal error is raised when a nullsafe access hits null — the whole expression simply evaluates to null.

  • Combine ?-> with ?? to supply a fallback value in a single readable line, replacing nested isset()/if chains.

  • ?-> works for both property access and method calls.

  • ?-> cannot appear on the left-hand side of an assignment — it is a read-only safety mechanism.

Tip
Reach for `?->` specifically when a chain of accesses might have a legitimately absent link partway through — an optional related object, a lazily-loaded relationship, an API response field that might be missing. If a value should never actually be `null` in correct program state, prefer letting a genuine `null` there surface as an error rather than silently swallowing it with `?->`, since that can hide real bugs instead of just handling expected absence.