В интернете часто можно встретить вопрос, нужно ли передавать NULL в параметрах методов, а также возвращать NULL из методов. Если нет, то почему и как писать код в таких случаях? Об этом и пойдёт речь в статье.
В чём вообще проблема использования null значений? Дело в том, что NULL можно трактовать по разному — это когда значение неизвестно, его используют в качестве значений по умолчанию для параметров методов, возвращают когда не смогли получить успешный результат или когда что-то произошло не так. Есть куча способов и причин использовать NULL. Однако это не всегда приемлемо.
Передача null в параметры
Передавать NULL в параметры метода может быть полезно, если нужно указать отсутствие значения или инициализировать параметр с нулевым значением по умолчанию.
Например, предположим, что у вас есть метод, который принимает параметр $name:
<?php
public function greet($name) {
echo "Hello, $name!";
}Если вы хотите обрабатывать случаи, когда он не указан, вы можете установить для параметра $name значение по умолчанию NULL:
<?php
public function greet($name = null) {
if ($name === null) {
echo "Hello, World!";
} else {
echo "Hello, $name!";
}
}В этом случае, если вы вызываете greet() метод без передачи каких-либо аргументов, он примет для параметра $name значение NULL по умолчанию.
Причина отказа от NULL заключается в том, что не нужно постоянно его проверять, если вы ожидаете в значении какое-то другое содержимое. Вместо этого вы можете вернуть, пустой массив, если ожидаемый результат — это массив. Если это целое число, возможно, будет достаточно 0, если это строка, то пустая строка и так далее.
Но есть и другие способы передачи и проверки аргументов метода. К примеру, можно передавать аргументы в виде массива:
<?php
function getParams(array $params) {
return [
'columns' => $buttonParameters['col'],
'rows' => $params['count']
'count' => $params['count']
];
}Другой вариант, не указывать аргументы функции явно, а воспользоваться динамическими аргументами. В этом поможет функция func_get_args(). И в теле уже самой функции просто валидировать получаемые функцией аргументы:
<?php
function getParamsFunction()
{
var_dump(func_get_args());
}
getParamsFunction(1, 2, 3, 7, 10, 12);Дополнительно можно использовать функции func_num_args() и func_get_arg().
Однако эти два способа сильно ухудшают понимание кода и требует множество дополнительных проверок каждого параметра. Поэтому эти способы создают ещё больше хлопот, чем использование NULL по умолчанию. Тем не менее эти способы частенько можно встретить.
В PHP 8 появились именованные аргументы. Теперь передать параметры можно следующим образом:
<?php
function params($a = 1, $b = 2) {
print "a: $a, b: $b";
}
params(b: 5); // Выведет "a: 1, b: 5"В этом случае аргументы функции передаются по имени параметра, а не по его позиции. Аргумент не зависит от порядка и указанного значения по умолчанию. Именованный аргумент передаётся с помощью добавления двоеточия перед значением имени параметра. Для имени параметра можно использовать зарезервированные слова. Однако имя не может быть создано динамически и должно быть идентификатором.
Возврат null из метода
Обычно из функции возвращают false, если что-то пошло не так. Аналогичное делают и стандартные функции PHP. Для этих же целей возвращают NULL, пустую строку, пустой массив или вообще ничего. В общем, у каждого свой поход и причины возвращать то или иное значение. Многие даже как-то не сильно об этом задумываются. Однако в некоторых случаях, возврат NULL как и возврат других значений, которые изначально не подразумевались для возврата, могут приводить к ошибкам и непонятным замешательствам. Здесь конечно же серьёзную роль играет сам PHP, поскольку типы данных в нём слабо типизированы и мы можем вертеть данными как угодно.
К примеру, возьмем такой метод:
<?php
function getClients()
{
$query = 'SELECT * FROM clients';
$result = $this—>mysqli—>query($query);
if ($result) {
return false;
}
$data = [];
while ($row = $result->fetch_assoc())
{
$data[] = $row;
}
return $data;
}В нём мы собираемся получить список клиентов из базы данных. Если клиенты успешно получены, мы возвращаем массив, а если что-то пошло не так, то метод возвращает false. Затем мы используем данный метод, чтобы далее проводить операции с клиентами:
<?php
$result = $db—>getClients();Вот здесь и начинаются первые сложности, поскольку метод возвращает несколько типов данных: array и false. Поэтому нужно дополнительно определить, что именно было возвращено. Вроде бы здесь нет ничего такого необычного и проверка данных может показаться обычным делом, но здесь как раз возможно ошибиться на проверке типа данных.
<?php
$result = $db—>getClients();
if (!$result) {
// что-то произошло не так
}
// работаем с массивом клиентовВ этом примере идёт неявная проверка данных, поэтому даже если функция getClients() возвращает пустой массив, то условие сработает. Поэтому лучше проверять на конкретный тип данных:
<?php
$result = $db—>getClients();
if ($result === false) {
// что-то произошло не так
}
if (count($result) === 0) {
// пустой результат
}
// а если не false и array не пустой, то работаем с массивом клиентовЕсли мы используем вместо false другое значение, пустую строку или NULL, то ситуация может возникнуть практически такая же. Как в таком случае лучше изменить код и что использовать?
Вместо возврата false, пустой строки или NULL можно использовать исключения. Это позволит сразу же определить, что именно и на каком моменте произошло.
<?php
function getClients()
{
$query = 'SELECT * FROM clients';
$result = $this—>mysqli—>query($query);
if ($result) {
throw new ErrorException('Произошла такая-то ошибка');
}
$data = [];
while ($row = $result->fetch_assoc())
{
$data[] = $row;
}
return $data;
}Затем при использовании метода обработать это исключение:
<?php
try {
$result = $db—>getClients();
// все норм, двигаемся дальше
} catch (ErrorException $e) {
// схватили ошибку!
}Здесь уже не нужно дополнительно проверять на всякие false или NULL и можно быть точно уверенным, почему метод не отдал правильные данные. Важно лишь правильно перехватывать и обрабатывать такие исключения.
<?php
public getFileSize(File $file) {
if (!$file—>exists()) {
return null;
}
return $file—>getLength();
}В этом простом примере метод возвращает размер файла. Если файла не существует, то возвращается NULL, 0 или пустая строка — неважно. Вот здесь и начинается проблема, что файла не существует, он был перемещён или удалён, но вместо того чтобы узнать об этом, подобная проверка маскирует проблему. В этом случае можно использовать исключение:
<?php
public getFileSize(File $file) {
if (!$file—>exists()) {
throw new Exception(
"Нельзя получить размер файла. Файл не найден!"
);
}
return $file—>getLength();
}Начиная с PHP7 для функций можно установить тип возвращаемого значения. Например:
<?php
function getValue(): int {
return 777;
}Результат:
int(777)Если указать цифру строкой:
<?php
function getValue(): int {
return '777';
}То она автоматически будет преобразована в int.
Результат:
int(777)Чтобы включить строгую типизацию, нужно установить:
declare(strict_types=1);Тогда если попытаться задействовать в этой функции строку, будет выдана ошибка:
Uncaught TypeError: Return value of getValue() must be of the type integer, string returned
Начиная с PHP 7.1 можно указать тип возвращаемого значения, допускающий значение NULL. Для этого нужно поставить вопросительный знак перед типом возвращаемого значения. Таким образом, функция может вернуть целое или нулевое значение.
<?php
function getValue() : ?int {
return null;
}Кроме этого, если в методе вообще не использовать return или просто указать его без всякого значения, метод вернёт NULL. Например:
<?php
function getValue1(){
return;
}
function getValue2() {
}Поскольку return;и return null; эквивалентны в PHP, то если возвращаемое значение не указано, PHP выполнит работу NULL за вас. То же самое касается и свойств объектов. По умолчанию они имеют значение NULL до тех пор, пока не будут заданы. Поскольку это поведение по умолчанию, лучше помнить об этом и проверять свойства, которые имеет ваш класс, прежде чем совершать вызовы или что-то ещё.
В большинстве случаев рекомендуется возвращать Null-object который предоставляет поведение по умолчанию в зависимости от того как реагирует ваш код на ситуацию когда ничего не найдено.
Так что же в итоге возвращать?
Все индивидуально, зависит от проекта. Главное следовать установленной спецификации в рамках проекта, чтобы соблюдался единый стиль. Проверка нулевых значений — обычная практика, иначе вы рискуете повсюду получить исключения. Лучше корректно обработать ошибку, чем генерировать исключения, когда в этом нет необходимости. NULL это не обязательно плохо, как и в случае со многими другими решениями, это зависит от ситуации.
Использование NULL может быть допустимо, когда это целесообразно и не противоречит остальным частям проекта. В большинстве случаев null следует либо заменить выдачей исключения, либо предоставлением нулевого объекта.
Плохое использование NULL, это когда идет попытка заменить или скрыть исключительные ситуации, такие как: ошибка добавления записи в бд, подключение к какому-то API, неверные параметры и так далее. В этих случаях создание исключения более адекватно, поскольку значение NULL не дает значимой информации об ошибке и любые проверки игнорируют проблему.
