PHP Ternary Operator: Nested Ternaries, ?:, ?? and match

Quick Summary

  • Write condition ? value_if_true : value_if_false to pick one of two values; since PHP 8 a nested ternary needs parentheses around the inner one, or the file stops with a fatal error.
  • $a ?: $b falls back on any falsy value, while $a ?? $b falls back only when $a is missing or null and gives no warning; $a ??= $b assigns a default the same way.
  • For three or more outcomes, use a PHP 8 match expression: it compares with ===, returns a value and throws UnhandledMatchError when nothing matches and there is no default.

The ternary operator is a short way to write an if/else that picks a value: condition ? value_if_true : value_if_false. You can nest a second ternary inside the first, but since PHP 8 the inner one must be wrapped in parentheses, or the whole file stops with a fatal error. For “this value or a default”, PHP has shorter operators: ?:, ?? and ??=. With three or more outcomes, a match expression (PHP 8.0 and later) is easier to read than a nested ternary. The outputs shown below are from PHP 8.2 and 8.4.

How does the ternary operator work?

This snippet shows the same check three ways: a normal if/else if/else chain, a simple ternary and a nested ternary.

PHP
//normal
$n = 12;
$ns = 12;
if( 12 == $n ){
    echo "Twelve";
}else if( 10 == $n ){
    echo "Ten";
} else {
    echo "Just a number";
}

echo "\n";

$numberInWord = (12 == $n ) ? "Twelve" : "Just a number";
$numberInWordNested = ( 12 == $ns ) ? "Twelve" : ( ( 10 == $ns ) ? "Ten" : "Just a number" );
echo $numberInWord;
echo "\n";
echo $numberInWordNested;

It prints Twelve three times, one per line.

  • The if block prints Twelve because $n is 12. It also has a branch for 10 and a fallback.
  • $numberInWord uses a single ternary: if 12 == $n it gets Twelve, otherwise Just a number. Unlike the if block, it has no check for 10.
  • $numberInWordNested adds the check for 10 by putting a second ternary, wrapped in parentheses, in the false branch of the first.
  • Writing the number first (12 == $n, a Yoda condition) means a typo like 12 = $n causes an error instead of silently assigning a value.

How do you nest ternary operators safely?

Before PHP 8, the ternary operator was left-associative, so without the inner parentheses the nested line was read as ( ( 12 == $ns ) ? "Twelve" : ( 10 == $ns ) ) ? "Ten" : "Just a number". On PHP 7 that returns Ten when $ns is 12. PHP 7.4 deprecated this form, and PHP 8 doesn’t run it at all: the file stops when it’s compiled, before its first line runs, with this message:

Code
Fatal error: Unparenthesized `a ? b : c ? d : e` is not supported. Use either `(a ? b : c) ? d : e` or `a ? b : (c ? d : e)`

That’s why the snippet wraps the inner ternary in parentheses: ( 12 == $ns ) ? "Twelve" : ( ( 10 == $ns ) ? "Ten" : "Just a number" ). The same rule applies when you mix a full ternary with the short form: $a ?: $b ? $c : $d stops with the same kind of error. One nested level is still readable; beyond that, use match, shown below.

What does the short ternary ?: do?

$a ?: $b returns $a if it’s truthy and $b otherwise. It’s short for $a ? $a : $b, except that $a is evaluated only once.

PHP
$title = '';
echo $title ?: 'Untitled'; // Untitled
echo "\n";

$qty = 0;
echo $qty ?: 'none'; // none: 0 is falsy too
echo "\n";

$a = 0;
$b = '';
$c = 'third';
echo $a ?: $b ?: $c; // third: the first truthy value

Every falsy value falls through to the default: '', '0', 0, 0.0, null, false and an empty array. You can chain several ?: without parentheses. Unlike ??, it still warns when the variable or array key doesn’t exist: when the URL has no sort parameter, $_GET['sort'] ?: 'date' returns date but also prints the warning Undefined array key "sort".

What does the null coalescing operator ?? do?

$a ?? $b returns $a if it exists and isn’t null, and $b otherwise. It checks the left side like isset() does, so a missing variable or array key gives no warning. That makes it the usual way to read a request value with a default (PHP 7.0 and later):

PHP
$query = [ 'page' => '0' ]; // for example $_GET for the URL ?page=0

$page = $query['page'] ?? 1;
var_dump( $page ); // string(1) "0": the key exists, so its value is kept

$sort = $query['sort'] ?? 'date';
var_dump( $sort ); // string(4) "date": the key is missing, and there's no warning

var_dump( $query['page'] ?: 1 ); // int(1): ?: treats "0" as false

$settings = [ 'color' => null ];
var_dump( $settings['color'] ?? 'blue' );         // string(4) "blue": null counts as missing
var_dump( $settings['size']['width'] ?? 'auto' ); // string(4) "auto": nested keys work too

echo $user_name ?? 'Guest'; // Guest: an undefined variable, no warning

Use ?? when only a missing or null value should get the default, and ?: when empty strings and zeros should get it too.

How does ??= work?

$a ??= $b (PHP 7.4 and later) assigns $b only when $a is missing or null, which makes it a short way to fill in defaults:

PHP
$options = [ 'color' => 'red' ];

$options['color'] ??= 'blue';   // already set: stays red
$options['size']  ??= 'medium'; // missing: becomes medium

print_r( $options ); // [color] => red, [size] => medium

match: the PHP 8 alternative to nested ternaries

A match expression compares one value with a list of arms and returns the result of the first arm that matches. Here is the nested ternary from the first example written with it:

PHP
$ns = 12;

$numberInWord = match ( $ns ) {
    12      => 'Twelve',
    10      => 'Ten',
    default => 'Just a number',
};

echo $numberInWord; // Twelve

How match differs from a switch or a chain of ternaries:

  • It returns a value, so you assign it directly, and there’s no break to forget.
  • It compares with ===: the string '12' doesn’t match the arm 12. A switch compares with ==, where '12' == 12 is true.
  • One arm can list several values: 'sat', 'sun' => 'Weekend'.
  • If no arm matches and there’s no default, PHP throws an UnhandledMatchError (“Unhandled match case 7”) instead of quietly returning nothing.

For ranges, match against true: the first arm whose condition is true wins.

PHP
$score = 72;

$grade = match ( true ) {
    $score >= 80 => 'A',
    $score >= 60 => 'B',
    $score >= 40 => 'C',
    default      => 'F',
};

echo $grade; // B

Use a ternary for two outcomes, match when one value decides between several results, and if/elseif or a switch statement when each branch has to run several statements rather than return a value.

Why does my ternary return the wrong value?

Usually it’s operator precedence. The ternary operator and ?? have lower precedence than the . that joins strings, so PHP joins first and tests the result:

PHP
$is_member = false;
echo 'Status: ' . $is_member ? 'member' : 'guest';    // member
echo "\n";
echo 'Status: ' . ( $is_member ? 'member' : 'guest' ); // Status: guest
echo "\n";

$name = null;
echo 'Hello ' . $name ?? 'Guest';     // Hello
echo "\n";
echo 'Hello ' . ( $name ?? 'Guest' ); // Hello Guest

The first line tests 'Status: ' . $is_member, a non-empty string, so the condition is always true and only member is printed. The third line hands 'Hello ' to ??, which isn’t null, so the default is never used. Wrap a ternary or ?? in parentheses whenever it’s part of a longer expression.

Share on X (opens in a new tab) Share on LinkedIn (opens in a new tab) Share on Facebook (opens in a new tab)

ChatGPT (opens in a new tab) Claude (opens in a new tab) Gemini (opens in a new tab) Perplexity (opens in a new tab) Grok (opens in a new tab)

More Insights

See More