В PHP 8 началось ужесточение проверки аргументов встроенных функций: с каждой новой версией требования к вызовам распространённых функций становятся строже. Прошли времена, когда можно было передать null в функцию, ожидающую целое число, и надеяться, что всё сработает. Сегодня usort($arr, null) или array_walk($arr, null) сразу завершатся с TypeError.
Команда PHP последовательно убирает null из сигнатур встроенных функций. Это делает код более предсказуемым и последовательным.
Но, как это часто бывает в PHP, глубоко внутри движка на уровне C всё ещё остаётся небольшая группа исключений. Здесь null используется не просто как значение по умолчанию. Это то, что можно назвать "магическим null" — специальное значение-сигнал, которое меняет поведение функции и означает нечто принципиально отличающееся от 0, пустой строки или другого пустого значения.
Таких случаев немного, и некоторые из них выглядят необычно, но они могут быть очень полезны, если знать об их существовании.
1. array_map(null, ...): безымянный zip
Обычно array_map принимает callback и применяет его к одному или нескольким массивам. Но если передать в качестве callback null, PHP полностью отказывается от идеи преобразования элементов и превращает функцию в аналог zip.
Функция берёт первый элемент каждого массива и помещает их в новый массив, затем второй элемент и так далее.
<?php
$keys = ['id', 'name'];
$values = [1, 'Alice'];
$zipped = array_map(null, $keys, $values);
// Result: [['id', 1], ['name', 'Alice']]
?>Это штатный способ объединить массивы в PHP без написания собственного цикла. По сути, он является противоположностью array_column.
<?php
$source = [['id', 1], ['name', 'Alice']];
$keys = array_column($source, 0); // ['id', 'name']
$values = array_column($source, 1); // [1, 'Alice']
$zipped = array_map(null, $keys, $values);
// Result: [['id', 1], ['name', 'Alice']] === $source
?>Всё это относится именно к массивам, но здесь стоит думать о них как о списках, а не о хеш-таблицах или map-структурах.
2. array_filter($arr, null): пустой фильтр
Скорее всего, вы не раз использовали array_filter с замыканием, чтобы удалить пустые значения. Но замыкание здесь можно вообще не передавать.
null во втором аргументе запускает специальный режим, который автоматически удаляет все значения, считающиеся ложными: false, 0, '', null и []. Для проверки используется поведение empty().
<?php
$data = ['apple', '', 'banana', null, 0, 'cherry'];
$filtered = array_filter($data, null);
// Result: ['apple', 'banana', 'cherry']
?>Это небольшой, но удобный сокращённый вариант: Filter this array, using nothing.
3. array_column($arr, null, $key): мгновенный hashmap
array_column известна прежде всего как функция для извлечения одного столбца из двумерного массива в плоский список. Выше уже был пример такого использования.
Но если передать null в качестве второго аргумента, функция ничего не извлекает. Вместо этого она возвращает весь двумерный массив и переиндексирует его по столбцу, указанному третьим аргументом.
<?php
$users = [
['id' => 101, 'name' => 'Alice'],
['id' => 102, 'name' => 'Bob'],
];
$lookup = array_column($users, null, 'id');
// Result: [101 => ['id' => 101, 'name' => 'Alice'], ...]
?>Это самый быстрый способ превратить последовательный набор результатов из базы данных в словарь для поиска с доступом за O(1).
4. array_slice($arr, $offset, null): срез без ограничения
Третий аргумент array_slice, $length, выглядит как обычное число: если передать 3, функция вернёт три элемента, а если передать 0, логично ожидать пустой результат.
null намеренно нарушает эту логику. Он не означает «ноль элементов», а говорит: "без ограничения, взять всё до конца массива".
Это единственное значение этого параметра, которое фактически не является длиной. Оно указывает функции полностью проигнорировать концепцию ограничения длины.
Необычность здесь в том, что в большинстве случаев null действительно означает 0, но в этом случае он имеет совершенно другое значение.
<?php
$a = [1, 2, 3, 4, 5];
array_slice($a, 1, null); // [2, 3, 4, 5] <- всё до конца
array_slice($a, 1, 0); // [] <- пустой срез
?>Это позволяет избежать распространённой ошибки: использовать 0, когда на самом деле требуется отсутствие ограничения, и в результате незаметно получить пустой массив.
Такое же соглашение используется в mb_substr() и array_splice() для параметров, отвечающих за длину.
5. error_reporting(null): тихая проверка
Некоторые конфигурационные функции PHP используют один параметр одновременно как getter и setter. error_reporting() — наиболее наглядный пример.
Если передать реальный уровень обработки ошибок, функция изменит текущую настройку. Если передать null, она просто вернёт текущее значение, не изменяя его.
Важно, что null здесь не является обычным значением по умолчанию. Он полностью отличается от другого значения, которое можно было бы принять за аналогичный вариант 0. Ноль является допустимым уровнем error_reporting и отключает вывод всех ошибок.
<?php
error_reporting(E_ALL);
error_reporting(null); // 30719 — читает текущий уровень и ничего не меняет
error_reporting(0); // 30719 — возвращает старый уровень, но устанавливает новый: 0
?>Если вызвать эти варианты один за другим, результат будет совершенно разным: в первом случае PHP продолжит сообщать обо всех ошибках, а во втором перестанет их сообщать.
Перепутать error_reporting(null) с error_reporting(0) легко, и это вполне может стать реальной ошибкой в коде.
Такое же соглашение "передать null, чтобы получить текущее значение, передать значение, чтобы его изменить" встречается у целого семейства функций PHP: ignore_user_abort(null), mb_internal_encoding(null), mb_regex_encoding(null), libxml_use_internal_errors(null), а также у функций настройки сессий, таких как session_cache_limiter(null), session_save_path(null) и session_name(null).
Ни одной из них не требуется аргумент, чтобы работать как getter, но все они принимают явный null как документированный способ сообщить: нужно только проверить текущее состояние.
6. set_error_handler(null): сброс обработчика
Можно предположить, что передача null в функцию установки обработчика ошибок либо отключит обработку ошибок, либо отменит последнее изменение и восстановит обработчик, который был активен до этого. Не происходит ни того, ни другого.
Документация прямо указывает, что передача null сбрасывает обработчик в состояние по умолчанию. То есть используется встроенный обработчик PHP, а не тот, который был активен непосредственно перед этим.
Получается, что обработчик ошибок всегда должен существовать. Один обработчик сменяет другой.
<?php
set_error_handler('handlerA');
set_error_handler('handlerB');
set_error_handler(null);
// Ни handlerA, ни handlerB больше не вызываются —
// используется встроенный обработчик ошибок PHP.
restore_error_handler(); // Возвращает handlerB.
restore_error_handler(); // А этот вызов возвращает handlerA.
?>Внутри действительно существует стек обработчиков. Каждый вызов set_error_handler() помещает предыдущий обработчик в этот стек, в том числе когда в качестве аргумента передаётся null.
Поэтому предположение о том, что здесь используется нечто похожее на стек, верно. Но сам null означает именно сброс, а не отмену последнего изменения. Для отмены используется restore_error_handler().
Если перепутать эти два вызова, можно столкнуться с особенностью, о которой предупреждают пользовательские заметки в документации: многократный вызов set_error_handler(null) вместо restore_error_handler() незаметно добавляет новые записи в стек.
Такое же поведение используется в set_exception_handler(null).
RFC на будущее: usort($array, null)
Если рассмотреть описанные случаи вместе, становится заметна общая закономерность. PHP уже использует null как допустимый способ сказать: не использовать пользовательское поведение, а применить очевидное встроенное значение по умолчанию, как в array_map, array_filter, array_column и array_slice.
В других случаях null означает: ничего не менять, просто сообщить текущее состояние, как в error_reporting() и связанных с ней функциях, либо сбросить состояние до известного значения по умолчанию, как в set_error_handler().
Это не набор случайных исключений, а соглашение, которое существует в PHP уже давно.
usort(), uasort() и uksort() остаются исключениями. Их параметр callback имеет обычный тип callable и не допускает null. Поэтому usort($arr, null) это тот самый вызов, который на всех поддерживаемых версиях PHP приводит к TypeError.
<?php
usort($nums, null);
// TypeError: usort(): Argument #2 ($callback)
// must be a valid callback, no array or string given
?>Но что должно означать без пользовательского компаратора для функции сортировки?
В стандартной библиотеке PHP уже есть ответ — обычная sort(), которая сортирует элементы с использованием встроенных правил сравнения PHP, фактически через оператор <=>, известный как "космический корабль".
Здесь нет неоднозначности: поведение по умолчанию уже существует и доступно через отдельный вызов функции.
Предлагаемый в RFC вариант заключается в том, чтобы сделать callback nullable для usort(), uasort() и uksort(), а при передаче null использовать тот же компаратор, который уже применяется внутри sort(), asort() и ksort().
<?php
$nums = [3, 1, 2];
// Сегодня «без пользовательского компаратора»
// на практике выглядит так:
usort($nums, fn($a, $b) => $a <=> $b);
// Предлагаемый вариант:
// тот же результат, в соответствии с поведением
// array_map(null, ...) и array_filter($arr, null),
// без необходимости обходить TypeError.
usort($nums, null);
?>Это не добавляет возможностей, которых разработчик уже не может получить одной строкой. Например, array_filter($arr, null) не делает ничего такого, что нельзя было бы реализовать самостоятельно через array_filter($arr, fn($v) => (bool) $v).
Ценность заключается в том, чтобы превратить распространённый приём в документированное поведение по умолчанию и сделать null допустимым и намеренным аргументом для usort(), а не ловушкой, о которой предупреждает начало статьи.