0

Ternary operator, null coalescing, and short-circuit evaluation

Beginner5 min read·php-05-006
interview

Concept

PHP provides several compact conditional and logical operators that, when used correctly, make code more readable. Misused, they hide bugs.

Ternary ($a ? $b : $c): if $a is truthy, evaluates to $b, otherwise $c. Short ternary (Elvis operator, $a ?: $c): if $a is truthy, returns $a; otherwise $c. Used for "use value if truthy, otherwise fallback". Null coalescing ($a ?? $b): if $a exists and is not null, returns $a; otherwise $b. Does not trigger isset warnings on undefined variables — safer than Elvis for array keys and properties.

Null coalescing assignment (??=, PHP 7.4+): $a ??= $b is equivalent to $a = $a ?? $b. Use to lazily initialize values.

Short-circuit evaluation: && (and) stops evaluating at the first false operand. || (or) stops at the first true operand. This is not just an optimization — it prevents errors: if ($obj !== null && $obj->method()) is safe because $obj->method() is never called if $obj is null.

Operator precedence trap: and and or have lower precedence than =. $a = true or false assigns true to $a (the assignment happens first). $a = true || false correctly evaluates the || first. Always use && and ||, not and and or, except in specific idiomatic patterns.

Code Example

php
<?php
declare(strict_types=1);

// Ternary — simple conditional
$age = 20;
$label = $age >= 18 ? 'adult' : 'minor';

// Short ternary (Elvis) — use value if truthy, else fallback
$name = $_GET['name'] ?: 'Anonymous';    // '' or missing → 'Anonymous'
$port = $config['port'] ?: 3306;         // 0 or false → 3306 (careful with 0!)

// Null coalescing — safe undefined key/property access
$username = $_SESSION['user']['name'] ?? 'Guest'; // no E_NOTICE even if unset
$ttl      = $config['cache']['ttl'] ?? 3600;
$value    = $obj?->getProp() ?? 'default'; // nullsafe + null coalescing

// Null coalescing assignment — lazy initialization
$cache ??= new Cache();           // only creates if $cache is null
$request['count'] ??= 0;         // initialize array key only if unset
$request['count']++;

// Elvis vs Null coalescing — the key difference
$arr = ['value' => 0];

$a = $arr['value'] ?: 'default';  // 'default' — 0 is falsy!
$b = $arr['value'] ?? 'default';  // 0        — null coalescing only checks null/undefined

// Short-circuit evaluation — prevents errors
function expensive(): bool { echo "expensive called\n"; return true; }

$fast = false;
$result = $fast && expensive(); // expensive() never called — short-circuit

// Null check chain
class Order { public ?Customer $customer = null; }
class Customer { public ?Address $address = null; }
class Address { public string $city = ''; }

$order = new Order();
// Safe chaining with short-circuit
$city = ($order->customer !== null && $order->customer->address !== null)
    ? $order->customer->address->city
    : 'Unknown';

// Cleaner with nullsafe operator (PHP 8.0)
$city = $order->customer?->address?->city ?? 'Unknown';

// Precedence trap
$flag1 = true or false;   // $flag1 = true  (assignment before `or`)
$flag2 = true || false;   // $flag2 = true  (correct, || evaluated first)
var_dump($flag1, $flag2); // both true, but for different reasons