Null Coalescing ?? and ??=
?? returns the first value that exists and isn't null; ??= assigns only when the target is null. Both are silent about undefined keys.
The null coalescing operator ?? is the polite one: it reads a value that might not exist without ever emitting a warning. It's the modern replacement for sprawling isset() ladders.
Chaining and the ??= form
<?php
$config = [];
// first non-null wins
$timeout = $config['timeout'] ?? $env['TIMEOUT'] ?? 30;
// ??= sets a default only if the key is missing/null
$config['retries'] ??= 3;
echo $timeout; // 30
echo $config['retries']; // 3Remember the boundary: ?? only cares about null (and undefined). An empty string or 0 passes through untouched — that's usually exactly what you want, and where it differs from the short ternary ?:.
Example
When to use it
- Safely read deeply nested JSON config keys (e.g. $cfg['db']['host'] ?? 'localhost') without isset checks.
- Use ??= to lazily populate a cache array entry the first time a key is accessed.
- Chain multiple fallback sources for a setting: env var ?? config file value ?? hard-coded default.
More examples
Deep nested key access
?? suppresses undefined-index notices and returns the fallback; it works at every depth of nesting.
<?php
$config = ['database' => ['port' => 5432]];
$host = $config['database']['host'] ?? 'localhost';
$port = $config['database']['port'] ?? 3306;
$timeout = $config['database']['timeout'] ?? 30;
echo "$host:$port (timeout $timeout)"; // localhost:5432 (timeout 30)??= for lazy initialisation
??= only calls the right-hand side when the variable is null or unset, making it ideal for one-time cache population.
<?php
$cache = [];
function expensiveLoad(string $key): string {
echo "Loading $key\n";
return strtoupper($key);
}
$cache['foo'] ??= expensiveLoad('foo'); // runs
$cache['foo'] ??= expensiveLoad('foo'); // skipped
echo $cache['foo']; // FOOChained fallback sources
Chaining ?: and ?? creates a priority-ordered fallback: env variable, then config file, then hard-coded default.
<?php
$appConfig = ['log_level' => 'info'];
$level = getenv('LOG_LEVEL')
?: ($appConfig['log_level'] ?? 'warning');
echo $level; // 'info' (from appConfig; env not set)
Discussion