Null & Null-Safe Operators
Reviewed & published by Brayan K
Missing data is everywhere — absent array keys, unset properties, optional objects. PHP gives you three sharp tools for handling null gracefully: ?? for defaults, ??= for lazy assignment, and ?-> for safe object chains.
Part of the free PHP course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
What You'll Learn in This Lesson
- Understand what null is and how to test for it
- Provide fallbacks with the ?? operator
- Set lazy defaults with the ??= operator
- Tell ?? (null) apart from ?: (falsy)
- Chain optional object access with ?->
- Handle missing data without warnings or crashes
1️⃣ What null Really Is
null is the type that means "no value". A variable is null when you assign null, when a function returns nothing, or when you read an array key or property that doesn't exist. Test for it with === null or is_null() — and don't confuse "is null" with "is falsy": 0 and "" are falsy but are not null.
<?php
// null means "no value at all". A variable is null when:
$a = null; // 1) explicitly assigned null
$arr = ['name' => 'Ada'];
// $arr['email'] is unset -> reading it warns AND yields null
// is_null() and === null both test for null.
var_dump(is_null($a)); // bool(true)
var_dump($a === null); // bool(true)
// null is falsy, but DON'T confuse "is null" with "is falsy".
var_dump($a == false); // bool(true) — loose: both falsy
var_dump(0 === null); // bool(false) — 0 is NOT null
var_dump("" === null); // bool(false) — "" is NOT null
?>2️⃣ The ?? Operator
The null coalescing operator ?? returns the left side unless it's null or undefined, in which case it returns the right side — and it never emits an "undefined key" warning. It also chains, walking left to right until it finds a non-null value.
<?php
// ?? — the null coalescing operator: "use the left side, but if it's
// null OR undefined, use the right side instead". No warning, ever.
$config = ['theme' => 'dark'];
$theme = $config['theme'] ?? 'light'; // 'dark' (key exists)
$lang = $config['lang'] ?? 'en'; // 'en' (key missing, no warning)
echo "Theme: $theme\n"; // Theme: dark
echo "Lang: $lang\n"; // Lang: en
// Crucial difference from isset()-less access: ?? is null-AND-undefined safe.
// It also chains left to right, stopping at the first non-null value.
$pick = null ?? null ?? 'third' ?? 'fourth';
echo "$pick\n"; // third
?>3️⃣ The ??= Operator
The null coalescing assignment operator ??= writes a value only if the target is currently null or unset. It's the tidy shorthand for "set this default if nothing's there yet", and it's great for lazy initialisation and caching.
<?php
// ??= — the null coalescing ASSIGNMENT operator.
// "Assign the right side ONLY if the left side is currently null/unset."
$settings = [];
$settings['timeout'] ??= 30; // unset -> becomes 30
$settings['timeout'] ??= 99; // already 30 -> unchanged
echo $settings['timeout'] . "\n"; // 30
// Handy for lazy defaults / caching:
$cache = null;
$cache ??= "expensive result"; // computed once
$cache ??= "ignored"; // skipped, $cache already set
echo $cache . "\n"; // expensive result
?>4️⃣ ?? vs ?: — Null vs Falsy
This is the distinction that trips people up. ?? only reacts to null; the short ternary ?: (the "Elvis" operator) reacts to anything falsy. For a value of 0 or "", the two behave completely differently — choose deliberately.
<?php
// ?? checks for NULL. The "Elvis" ?: checks for FALSY. They differ!
$count = 0;
echo ($count ?? 'none') . "\n"; // 0 — 0 is not null, so ?? keeps it
echo ($count ?: 'none') . "\n"; // none — 0 IS falsy, so ?: replaces it
$name = "";
echo ($name ?? 'guest') . "\n"; // (empty) — "" is not null
echo ($name ?: 'guest') . "\n"; // guest — "" is falsy
// Rule of thumb: use ?? for "missing/null", use ?: for "empty/falsy".
?>5️⃣ The Nullsafe Operator ?->
Added in PHP 8, the nullsafe operator ?-> reads a property or calls a method only if the object isn't null; if it is, the whole chain evaluates to null instead of throwing. It replaces piles of nested isset() checks for optional object graphs, and pairs perfectly with ?? for a final default.
<?php
// ?-> — the nullsafe operator (PHP 8). Call a method or read a property
// only if the object is NOT null; otherwise the whole chain is null —
// no "method on null" error.
class Address { public ?string $city = null; }
class User {
public ?Address $address = null;
public function __construct(?Address $a = null) { $this->address = $a; }
}
$withCity = new User(new Address());
$withCity->address->city = "Paris";
$noAddress = new User(); // address is null
// Before PHP 8 you'd need nested isset() checks. Now:
echo ($withCity->address?->city ?? 'unknown') . "\n"; // Paris
echo ($noAddress->address?->city ?? 'unknown') . "\n"; // unknown (no error!)
?>Now you try. Fill the ___ blanks with ?? or ??=.
<?php
// 🎯 YOUR TURN — fill each ___ to read settings safely.
$prefs = ['volume' => 0];
$volume = $prefs['volume'] ___ 50; // 👉 use ?? (keeps 0, it's not null)
$bright = $prefs['bright'] ___ 80; // 👉 use ?? (key missing -> 80)
$prefs['mode'] ___ 'auto'; // 👉 use ??= to set a default mode
echo $volume . "\n"; // 0
echo $bright . "\n"; // 80
echo $prefs['mode'] . "\n"; // auto
// ✅ Expected output:
// 0
// 80
// auto
?>🧩 Reorder Challenge
These scrambled lines should pick a display name and default a role. Put them in the order that prints Sam (member).
Create the array first, then derive $name and $role from it, then echo. The chain $user['nick'] ?? $user['name'] skips the null nickname and lands on "Sam", and the missing role key falls back to "member". Output: Sam (member).
🧠 Quick Recall
Predict the output before revealing each answer.
Prints 0. ?? only replaces null, and 0 is not null. With ?: instead, it would print none.
Prints 5. The first ??= sets $x to 5 because it was null; the second is skipped because $x is no longer null.
Evaluates to n/a. The ?-> short-circuits the null object to null (no error), then ?? supplies the default.
Common Errors (and the fix)
- A value of 0 gets replaced by your default — you used ?: when you meant ??. Use ?? to react only to null.
- "Attempt to read property on null" — you chained -> on a possibly-null object. Use ?->.
- "Undefined array key" still warns — you only guarded part of the access. The whole expression on the left of ?? is protected, so write $a['x']['y'] ?? null.
- You can't write $obj?->prop = 1 — nullsafe is read-only; you cannot assign through it.
Pro Tips
- 💡 Reach for ?? on every optional read. It both defaults and silences the missing-key warning.
- 💡 Remember: ?? = null, ?: = falsy. The difference matters most around 0 and "".
- 💡 Combine ?-> with ?? for clean optional chains: $a?->b?->c ?? 'default'.
📋 Quick Reference — Null Operators
| Operator | Reacts to | Example → result |
|---|---|---|
| ?? | null / undefined | $x ?? 'd' |
| ??= | null / undefined | $x ??= 'd' |
| ?: | any falsy value | $x ?: 'd' |
| ?-> | null object | $o?->m() |
| === null | exactly null | $x === null |
| is_null() | exactly null | is_null($x) |
Mini-Challenge: Build a Greeting
Write it yourself, run it on onecompiler.com/php or your own machine, then check against the expected output in the comments.
<?php
// 🎯 MINI-CHALLENGE: build a user greeting from a messy profile.
// No code filled in — follow the steps, then run and compare.
//
// Given: $profile = ['name' => 'Sam', 'nickname' => null];
//
// 1. $display = the nickname if it's set and non-null, else the name,
// else 'Anonymous'. Hint: $profile['nickname'] ?? $profile['name'] ?? 'Anonymous'.
// 2. $role = $profile['role'] ?? 'member' (key is missing).
// 3. echo "Welcome <display> (<role>)".
//
// ✅ Expected output:
// Welcome Sam (member)
// your code here
?>🎉 Lesson Complete!
- ✅ null means "no value"; test with === null
- ✅ ?? defaults null/undefined values without warnings and chains left to right
- ✅ ??= assigns only when the target is null/unset
- ✅ ?? reacts to null; ?: reacts to falsy — they differ at 0 and ""
- ✅ ?-> safely traverses possibly-null objects
Practice quiz
What does echo $count ?? 'none'; print when $count = 0?
- none
- (empty string)
- 0
- null
Answer: 0. ?? only replaces null or undefined. 0 is not null, so it is kept.
What does echo $count ?: 'none'; print when $count = 0?
- none
- 0
- false
- null
Answer: none. The ?: (Elvis) operator reacts to any falsy value, and 0 is falsy, so it is replaced.
After $x = null; $x ??= 5; $x ??= 9; what is $x?
- 9
- null
- 14
- 5
Answer: 5. The first ??= sets $x to 5 because it was null; the second is skipped because $x is no longer null.
What is the result of null ?? null ?? 'third' ?? 'fourth'?
- 'fourth'
- 'third'
- null
- An error
Answer: 'third'. ?? chains left to right and stops at the first non-null value, which is 'third'.
If $user->address is null, what is $user->address?->city ?? 'n/a'?
- 'n/a'
- null
- An 'attempt to read property on null' error
- 'city'
Answer: 'n/a'. ?-> short-circuits the null object to null without error, then ?? supplies the default 'n/a'.
Which expression is true?
- 0 === null
- "" === null
- null === null
- "0" === null
Answer: null === null. null === null is true. 0 and "" are falsy but are NOT null.
What does the ??= operator do?
- Always assigns the right side
- Assigns the right side only if the left side is null or unset
- Assigns only if the left side is falsy
- Compares two values for null
Answer: Assigns the right side only if the left side is null or unset. ??= is null coalescing assignment: $x ??= $d is shorthand for $x = $x ?? $d.
What does $config['lang'] ?? 'en' do when the 'lang' key is missing?
- Returns 'en' but emits an 'Undefined array key' warning
- Throws a TypeError
- Returns null
- Returns 'en' with no warning
Answer: Returns 'en' with no warning. ?? is null-and-undefined safe: it returns the default and never emits the undefined-key warning.
Which statement about the nullsafe operator ?-> is true?
- You can write $obj?->prop = 1 to assign safely
- It is read-only — you cannot assign through it
- It throws when the object is null
- It only works on arrays
Answer: It is read-only — you cannot assign through it. Nullsafe is read-only; $obj?->prop = 1 is not allowed. It safely reads or calls when the object is non-null.
When should you reach for ?? rather than ?:?
- When you mean 'empty or falsy'
- When comparing strings
- When you mean 'missing or null'
- When the value is always 0
Answer: When you mean 'missing or null'. Rule of thumb: ?? for 'missing/null', ?: for 'empty/falsy'. They differ around values like 0 and "".
Continue this course
- Previous: Superglobals ($_GET, $_POST, $_SERVER)
- Next: Checkpoint: PHP Basics — Combine variables, types, strings and math in a build challenge — then a quiz
- Quick reference: PHP cheat sheet
Frequently asked questions
What is the null coalescing operator ??
?? returns its left operand if that operand exists and is not null, otherwise it returns the right operand. Crucially it suppresses the 'Undefined variable/array key' warning, so $_GET['page'] ?? 1 is safe even when the key is absent. It also chains: $a ?? $b ?? 'default' walks left to right and stops at the first value that isn't null. Use it whenever you want a fallback for a possibly-missing or null value.
What is the difference between ?? and the ?: (Elvis) operator?
?? tests only for null (and undefined), while ?: tests for falsiness. That makes them behave differently for values like 0, "", "0", and []. For example $count ?? 'none' keeps 0 because 0 is not null, but $count ?: 'none' replaces it because 0 is falsy. The rule of thumb: reach for ?? when you mean 'missing or null', and ?: when you mean 'empty or falsy'. Mixing them up is a classic source of bugs around zero values.
What does the ??= operator do?
??= is the null coalescing assignment operator. $x ??= $default assigns $default to $x only if $x is currently null or undefined; if $x already holds a non-null value it's left untouched. It's the concise form of $x = $x ?? $default and is perfect for setting defaults on array keys or for lazy initialisation, where you compute an expensive value once and skip it on later passes.
What is the nullsafe operator ?-> and when should I use it?
?-> (PHP 8) lets you read a property or call a method on an object that might be null without crashing. In $user->address?->city, if $address is null the whole expression short-circuits to null instead of throwing an 'attempt to read property on null' error. It replaces deeply nested isset() checks for optional object chains. Pair it with ?? to supply a final default: $user->address?->city ?? 'unknown'.