Шпаргалка
Я люблю краткие записи и часто пользуюсь тернарным оператором, особенно в PHP. Добавление новых 2х вариаций тернарного оператора, таких как:
- ?: — сокращенная запись тернарного оператора
- ?? — null coalescing оператор
добавили приятного удобства, с одной стороны. С другой — я сам лично периодически путаюсь в этих операторах и забываю какой как должен себя повести. Но так же я видел что некоторые отъявленные php-гуру задают вопросы на по этим операторам. Решил немного привести знания в порядок и структурировать их.
Тернарный оператор
И так, тернарный оператор в классическом виде это не что иное как:
<?= true ? 'true' : false ? 'yes' : 'no' ?>
Выглядит вроде бы просто и понятно (или нет?). И такое можно встретить не только на собеседовании, но и в работе. Вопрос — а каков результат?
Если вычислять, то вроде бы по логике должно вывести слово true
. Но это не так. Будет выведено yes
. Как так?
Все дело в том, что тернарные выражения вычисляются слева направо. Поэтому эту запись более очевидно можно описать так:
<?= (true ? 'true':false) ? 'yes':'no' ?>
Вы можете видеть, что первое выражение вычисляется в true
, которое в свою очередь вычисляется в (bool)true
, таким образом возвращая истинную ветвь второго тернарного выражения.
Поэтому если увлекаетесь вложенными тернарными операторами (а я сам один из тех кто любит вложенные тернарные операторы) — то совет: обрамляйте логические блоки явно круглыми скобками, дабы не запутаться. Ну а если у вас корпоративная команда и вас очень много в проекте, то лучше и вовсе все писать явно длинно, используя классические блоки условий. Выглядит, как по мне, не так красиво, зато точно понятно.
Сокращенная запись тернарного оператора
Ранее часто в работе можно было видеть такие куски кода:
<?php declare(strict_types=1);
$getvar_a = $_GET['a'] ? $_GET['a'] : 'default';
Дабы сократить дублирование кода было решено, что можно убирать дублирующую запись и с версии 5.3 можно стало писать так:
<?php declare(strict_types=1);
$getvar_a = $_GET['a'] ?: 'default';
В целом удобно. И так, это всего лишь сокращенная запись тернарного оператора, подчиняющаяся всем правилам обычного тернарного оператора.
Вернемся к задачке с собеседования выше. Немного переформулируем ее так:
<?php declare(strict_types=1);
// 1:
var_dump( true ? true : 1 ? 1 : 'false' );
// 2: короткая запись
var_dump( true ?: 1 ?: 'false' );
Что же в итоге будет в первом и втором варианте? Ну вы уже понимаете, что ответ 1го варианта: 1. Логично предположить, что раз короткая запись это эквивалент, то второй вариант будет тоже 1, да? Но это не точно…
В короткой нотации будет ответ true. Добавим скобок, чтобы было понятен процесс обработки:
// 1:
var_dump( (true ? true : 1) ? 1 : 'false' );
// 2: короткая запись
var_dump( true ?: (1 ?: 'false') );
Имейте это в виду, если увлекаетесь вложенными тернарниками 😉
Возвращаясь к примеру с $_GET, если вдруг $_GET['a']
не будет существовать то мы получим предупреждение:
Notice: Undefined index: a in ...
Чтобы этого не было нужно было бы предварительно еще проверить наличие переменной или индекса. И вот чтобы это тоже сократить с версии 7 был добавлен null coalescing оператор.
Null coalescing оператор
Это еще одна разновидность тернарника, но с проверкой на существование (проверкой на null).
$getvar_a = $_GET['a'] ?: 'default';
// Notice: Undefined index: a in ...
// $getvar_a = 'default'
В то время как:
$getvar_a = $_GET['a'] ?? 'default';
// all ok
// $getvar_a = 'default'
// эквивалент
$getvar_a = (isset($_GET['a']) && $_GET['a']) ?: 'default';
В принципе нечего тут объяснять, все просто и понятно. Запомнили и поехали дальше.
Наша задачка с модификациями:
<?php declare(strict_types=1);
var_dump( true ?? 1 ?? 'false' );
Отработает так же как и в случае с короткой записью тернарного оператора.
Замечание
И на закуску еще одно важное замечание: все эти операторы трактуются как выражения, и их результат — это результат выражения. Это важно знать, если вы хотите вернуть переменную по ссылке:
<?php declare(strict_types=1);
$obj1 = f();
var_dump( $obj1 );
// Notice: Only variable references should be returned by reference in ...
function &f(): ?object {
static $obj;
if (!$obj) $obj = (object) ['foo' => 123];
//return $obj; // ok
return $obj ?: null; // notice
}