В работе с любыми данными то и дело требуется как-то обозначить их отсутствие. Новички зачастую для этой цели используют значение false, которое, по сути, является не отсутствием данных, а определённым значением типа boolean. Для того, чтобы как-то помечать неопределённость или отсутствие данных, существует специальное значение null. Про false, null и прочее уже немало сказано и добавить что-то оригинальное крайне сложно. Но гораздо сложнее бывает найти баг, вызванный неочевидным поведением этих штуковин. Поэтому, вдохновлённый одним таким багом, самым элегантным в моём послужном списке, я решил написать маленькую шпаргалку на эту тему.
Перегружать пост интересными, но довольно-таки бесполезными определениями из Википедии я не стану. Вместо этого перейду сразу к делу и приведу цитату со страницы php.net, посвящённой null:
Специальное значение NULL представляет собой переменную без значения. NULL - это единственно возможное значение типа null. Переменная считается null, если:
- ей была присвоена константа NULL.
- ей еще не было присвоено никакого значения.
- она была удалена с помощью unset().
До этого момента всё кажется простым, но чтобы так оставалось и дальше, при работе с null нужно придерживаться определённых правил.
Математические операции с null
Во всех математических операциях null ведёт себя аналогично int(0):
$a = null;
$a + 1; // int(1)
$a - 1; // int(-1)
$a * 1; // int(0)
1 / $a; // bool(false)
Проверка переменной на null
Чтобы точно узнать, что переменная содержит null (то есть, ничего не содержит), нужно использовать либо специальную функцию is_null() либо тождественное сравнение ===, а любые другие способы не подойдут:
// Правильная проверка переменной на null
$a = null;
is_null($a); // bool(true)
$a === null; // bool(true)
Проверка null if-выражениях, а так же в функциях empty() и isset()
Тут переменные с null ведут себя абсолютно предсказуемо, как и любые другие ложные значения (которые в if-выражениях приводятся к false). Но нужно помнить, что это не гарантирует нам, что в переменной находится именно null:
// В if-выражениях null приводится к false, как и все другие ложные значения
if($a) {
echo 'Sure, $a is not null!';
}
else {
echo 'Mabye $a is null.'
}
// Проверка на empty() вернёт true, но не гарантирует, что проверяемая переменная - именно null
$a = null; empty($a); // bool(true)
$a = []; empty($a); // bool(true)
$a = ''; empty($a); // bool(true)
$a = 0; empty($a); // bool(true)
$a = false; empty($a); // bool(true)
// Проверка isset() вернёт false не только для null, но для не определённой переменной
$a = null;
isset($a); // bool(false)
isset($undefined); // bool(false)
Сравнение двух null
В PHP как гибкое, так и тождественное сравнение двух null всегда возвращает false, в отличие от многих других платформ, где сравнение двух неизвестностей возвращает так же неизвестность (то есть, null).
// PHP уверен, что две неизвестности равны, и даже тождественно равны
$a = null; $b = null;
$a == $b; // bool(true)
$a === $b; // bool(true)
Поведение null при нетождественном сравнении с приведением типов данных
PHP разрешает сравнивать null-ы не только между собой, но и с другими типами данных. При этом не стоит забывать, что гибкое сравнение в php не является транзитивным, то есть, если два значения равны третьему по отдельности, не гарантирует их равенство между собой. Согласно таблицам приведения типов, гибкое сравнение null при помощи оператора == с разными значениями возвращает разные результаты:
// Для всех ложных значений гибкое сравнение с null возвращает true, кроме строки '0'
$a = []; $a == null; // bool(true)
$a = ''; $a == null; // bool(true)
$a = 0; $a == null; // bool(true)
$a = false; $a == null; // bool(true)
// А при сравнении null со строкой '0' мы получим false, хотя null == false и '0' == false
$a = '0'; $a == null; // bool(false)
// Для всех неложных значений такое сравнение так же вернёт false
$a = 1; $a == null; // bool(false)
$a = '1'; $a == null; // bool(false)
Сравнение с null с помощью >, <, >=, <=, сравнение с отрицательными числами и другие заковырки
Не мне судить, насколько это является очевидным, но при сравнении с числами null всегда оказывается меньше них. Смотрите сами:
// Не смотря на то, что null == 0, он оказывается меньше любого числа
null <= -1 // bool(true)
null <= 0 // bool(true)
null <= 1 // bool(true)
!(null >= -1) // bool(true)
null >= 0 // bool(true)
!(null >= 1) // bool(true)
На вид ничего особенного, но из этого следует, что если в дело вмешивается null, может пройти бессмысленная, на первый взгляд, проверка:
// Из-за подобного поведения я словил однажды баг, из-за которого почти поверил в магию :)
$a = null;
if($a <= -1 && $a >= 0) {
echo '$a <= -1 and $a >= 0 at the same time!';
}
Разное поведение null в PHP, SQL и JavaScript
А вот это, на мой взгляд, самое скверное. Реализуя логику работы с ячейками баз данных, в которых разрешено значение null, на разных этапах продвижения данных от сервера к пользователю (например, MySQL -> PHP -> JavaScript) поведение null может меняться.
Исследование null в JavaScript (а вместе с ним и загадочного undefined) заслуживает отдельной статьи. Главное отличие состоит в том, что, не смотря на приведение типов, null в JavaScript ничему не равен, кроме самого null и этого самого undefined, хотя в if-выражениях и срабатывает аналогично false. А при сравнении с числами он выдаёт ещё более забавные результаты, чем в PHP.
NULL в MySQL, к примеру, действует гораздо более прямолинейно. Он просто при любых действиях с null (даже при сравнении двух null) возвращает null. С его точки зрения, при любых действиях с неизвестностью в результате получится какая-то другая неизвестность :)
Простое правило при работе null, которое помогает избегать проблем
Вообще-то, не стоило читать этот пост. Держать в голове все эти нюансы, которые разные платформы трактуют по-разному, - неблагодарное занятие. Для того, чтобы избежать проблем с null нужно запомнить всего лишь одно правило:
Старайтесь не выполнять с null никаких лишних действий: не нужно его ни с чем сравнивать, складывать, сортировать. Там, где чисто теоретически переменная с null может просочиться в какие-то операторы, лучшим решением будет сперва проверить её функцией is_null().