Nullsafe Operator
What Is the Nullsafe Operator? In simple terms: When you follow a chain of objects — an order's customer's address's city — any link in the chain might be missing. Normally, calling a method or readin
What Is the Nullsafe Operator?
In simple terms: When you follow a chain of objects — an order's customer's address's city — any link in the chain might be missing. Normally, calling a method or reading a property on null causes an error, so you must check every step. The nullsafe operator ?-> does the checking for you: if the value on its left is null, the whole chain simply results in null.
Technically: The nullsafe operator (PHP 8.0+) works like the object operator ->, but if the left-hand side evaluates to null, it short-circuits: the rest of the chain is skipped — including method calls and the evaluation of their arguments — and the expression evaluates to null. It can be used for reading properties and calling methods, not for writing.
Before and After
// Before PHP 8.0
$city = null;
if ($order !== null) {
$customer = $order->getCustomer();
if ($customer !== null) {
$address = $customer->getAddress();
if ($address !== null) {
$city = $address->city;
}
}
}
// PHP 8.0+
$city = $order?->getCustomer()?->getAddress()?->city;
A First Example
<?php
final class Address
{
public function __construct(public string $city) {}
}
final class Customer
{
public function __construct(public string $name, private ?Address $address = null) {}
public function getAddress(): ?Address
{
return $this->address;
}
}
final class Order
{
public function __construct(public int $id, private ?Customer $customer = null) {}
public function getCustomer(): ?Customer
{
return $this->customer;
}
}
$orders = [
new Order(501, new Customer('Asha', new Address('Chennai'))),
new Order(502, new Customer('Bala')),
new Order(503),
];
foreach ($orders as $order) {
$city = $order->getCustomer()?->getAddress()?->city;
echo "Order {$order->id}: " . ($city ?? 'no city on file') . "\n";
}
Output
Order 501: Chennai
Order 502: no city on file
Order 503: no city on file
How It Works
- Order 501 has a customer with an address, so the full chain runs and returns
Chennai. - Order 502's customer has no address:
getAddress()returnsnull, so?->cityshort-circuits tonull. - Order 503 has no customer:
getCustomer()returnsnull, so everything after it is skipped. - The null coalescing operator
??turnsnullinto a readable fallback text.
Short-Circuiting Skips the Entire Chain
When ?-> finds null, it skips all remaining parts of the chain — even those written with the normal -> operator — and does not evaluate method arguments:
<?php
function expensiveLookup(): string
{
echo "expensiveLookup() was called\n";
return 'data';
}
final class Report
{
public function render(string $data): self
{
echo "Rendering $data\n";
return $this;
}
public function save(): string
{
return 'saved';
}
}
$report = null;
var_dump($report?->render(expensiveLookup())->save());
$report = new Report();
var_dump($report?->render(expensiveLookup())->save());
Output
NULL
expensiveLookup() was called
Rendering data
string(5) "saved"
- With
$report = null, the whole expression->render(expensiveLookup())->save()is skipped.expensiveLookup()is never called, and->save()does not cause an error even though it uses the normal->operator. - With a real object, the chain runs normally.
This means you only need ?-> at the points where null can actually occur; the rest of the chain is protected automatically.
?-> vs ??
The two operators are often used together, but they do different things:
| Operator | Checks | Use For |
|---|---|---|
?-> | Whether the object on the left is null | Continuing a chain only if the object exists |
?? | Whether the value on the left is null or undefined | Providing a default value |
$name = $user?->profile?->displayName ?? 'Guest';
?-> safely navigates the chain; ?? supplies the fallback.
There is one more difference: ?? also suppresses warnings for undefined variables, array keys, and properties. ?-> does not: it only reacts to null values. Accessing an undefined variable with $undefined?->name still produces an "Undefined variable" warning.
Where ?-> Cannot Be Used
| Context | Example | Result |
|---|---|---|
| Assignment | $order?->status = 'paid'; | Compile error: "Can't use nullsafe operator in write context" |
unset() | unset($order?->notes); | Compile error |
| References | $ref = &$order?->items; | Compile error |
| Static calls | ClassName?::method() | Not valid syntax |
Writing to a property of an object that may be null is a decision your code must make explicitly:
if ($order !== null) {
$order->markAsPaid();
}
Practical Example: Building a Delivery Label
<?php
final class Address
{
public function __construct(
public string $line1,
public string $city,
public ?string $pincode = null,
) {}
}
final class Customer
{
public function __construct(public string $name, public ?string $phone = null, public ?Address $address = null) {}
}
function deliveryLabel(?Customer $customer): string
{
$name = $customer?->name ?? 'UNKNOWN RECIPIENT';
$line1 = $customer?->address?->line1 ?? '(address missing)';
$city = $customer?->address?->city ?? '';
$pincode = $customer?->address?->pincode ?? '------';
$phone = $customer?->phone ?? 'no phone';
return sprintf("%s\n%s\n%s %s\nPhone: %s", $name, $line1, $city, $pincode, $phone);
}
echo deliveryLabel(new Customer('Asha', '98765 43210', new Address('12 Lake Road', 'Chennai', '600041'))) . "\n\n";
echo deliveryLabel(new Customer('Bala')) . "\n";
Output
Asha
12 Lake Road
Chennai 600041
Phone: 98765 43210
Bala
(address missing)
------
Phone: no phone
The label function handles missing customers, addresses, pincodes, and phone numbers without a single if statement. Each value falls back to a placeholder that warehouse staff will notice.
(In a real application, an order without a delivery address should not reach the label printer at all — validate required data earlier, and use ?-> for genuinely optional information.)
Common Mistakes
Overusing ?->
Effect: Writing ?-> everywhere hides bugs where a value should never be null.
Fix: Use it only where null is a legitimate, expected state; let errors surface elsewhere.
Expecting ?-> to Handle Undefined Values
Effect: Warnings for undefined variables or properties.
Fix: Ensure variables exist; use ?? for possibly undefined array keys.
Trying to Assign Through ?->
Effect: Compile error.
Fix: Check for null explicitly before writing.
Forgetting the Fallback
Effect: null flows further into the code (for example, printed as an empty string).
Fix: Combine with ?? to provide meaningful defaults.
Best Practices
- Use
?->to navigate chains of optional objects concisely. - Place it only at the links that can actually be
null. - Combine it with
??to provide defaults. - Keep write operations explicit with
ifchecks. - Prefer designs that avoid
nullwhere possible (required data, Null Objects).
Official reference: https\://www.php.net/manual/en/language.oop5.basic.php#language.oop5.basic.nullsafe