PHP 8.0 — Nullsafe operator (?->)
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
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