0

PHP 8.0 — Nullsafe operator (?->)

Beginner5 min read·php-09-005
interview

Concept

The nullsafe operator ?-> (PHP 8.0) short-circuits a method call chain when any step returns null. If the left-hand side is null, the entire expression evaluates to null without throwing a TypeError or Error. It's the PHP equivalent of optional chaining in JavaScript/Swift.

Before PHP 8.0: To safely traverse a chain like $user->getAddress()->getCity() where any step might return null, you needed nested null checks: $city = $user ? ($user->getAddress() ? $user->getAddress()->getCity() : null) : null. Or with null coalescing: $city = $user?->getAddress()?->getCity() ?? 'Unknown'.

How it works: $a?->method() — if $a is null, returns null immediately without calling method(). If $a is not null, calls $a->method() normally. The chain $a?->b()?->c() short-circuits at the first null.

What it does NOT do: ?-> only guards against null — it does not guard against undefined variables (use ?? for that) or exceptions thrown by the method. It also doesn't work on static method calls — use regular null check for those.

Combining with null coalescing: $city = $user?->getAddress()?->getCity() ?? 'Unknown' is the full pattern: nullsafe chain for the traversal, ?? to provide the default value at the end.

Code Example

php
<?php
declare(strict_types=1);

class Country { public function __construct(public string $name) {} }
class Address
{
    public function __construct(
        public string $city,
        public ?Country $country = null
    ) {}
    public function getCountry(): ?Country { return $this->country; }
}
class User
{
    public function __construct(
        public string $name,
        public ?Address $address = null
    ) {}
    public function getAddress(): ?Address { return $this->address; }
}

// Without nullsafe — verbose and error-prone
function getCityOld(?User $user): string
{
    if ($user === null) return 'Unknown';
    $address = $user->getAddress();
    if ($address === null) return 'Unknown';
    return $address->city;
}

// With nullsafe operator — clean and concise
function getCity(?User $user): string
{
    return $user?->getAddress()?->city ?? 'Unknown';
}

// Deep chain
function getCountryName(?User $user): string
{
    return $user?->getAddress()?->getCountry()?->name ?? 'Unknown';
}

$alice  = new User('Alice', new Address('Paris', new Country('France')));
$bob    = new User('Bob', new Address('New York'));  // no country
$carol  = new User('Carol');                          // no address
$nobody = null;

echo getCountryName($alice);  // "France"
echo getCountryName($bob);    // "Unknown" (no country)
echo getCountryName($carol);  // "Unknown" (no address)
echo getCountryName($nobody); // "Unknown" (null user)

// Works with method calls, not just property access
class Builder
{
    public ?BuilderOptions $options = null;
    public function getOptions(): ?BuilderOptions { return $this->options; }
}
class BuilderOptions { public int $timeout = 30; }

$builder = new Builder();
$timeout = $builder->getOptions()?->timeout ?? 30; // 30 (options is null)

// Limitation — static calls don't support ?->
// ClassName::?method() — not valid PHP
// Use: $obj?->staticLookingMethod() for instance context