Muranx
@Muranx
кто понял this тот в цирке не смеётся

Как правильно подходить к возвращаемым типам функциях php?

Здравствуйте!

Строгая типизация для меня вещь новая, уже ни раз встречал следующиее...
функция должна возаращать какой то один тип

если ваша функция не возвращает конкретный тип, скорее всего нарушается принцип единой ответственности, и вам нужно переписывать функцию

В целом где то глубоко в душе я понимаю, что действительно, когда функция возвращает конкретный тип, это выглядит более стабильно и безопасно а главное предсказуемо и проще (наверное) в будущем при поиске багов. Но в некоторых ситуациях я и хочу делать правильно, но появляются вопросы.. например

function getUserDataFromDatabase( int $userId ) : array {
         # вытягиваем данные пользователя из БД по его ид
         return $result;
};

функция простая, и всё хорошо, пока всё хорошо, но запрос в БД может вернуть в данном случае к примеру null, в таком случае произойдет ошибка, т.е. конечно можно до этой функции проверить есть ли такой ид пользователя, или к примеру обернуть вызов данной функции в try{}catch( Exception $e){}, но наверное всякое возможно.
ещё один примерчик. Допустим мне нужно проверить , есть ли пользователь с таким логином и паролем в БД , ну и в дальнейшем записать его айдишник в $_SESSION["id"];, предполагаю что можно сделать отдельную функцию , которая возвращает bool тип отвечающий на вопрос есть ли пользователь в БД, или просто сделать универсальную функцию, которая возвращает массив с данными пользователя, а уже этот массив просто преобразовать в булиевый тип (типа функции представленной выше, только с переданными логином и паролем)
function userIsPresentInDb( string $login, string $password) : bool {
         # функция возвращает в данном случае ответ "есть такой " или "нет"
         return gettype( $result ) === "array";
};

Вопрос в том, как правильно рассуждать в данном случае? Ведь в случае с запросами к БД типизация приведёт к увеличению количества запросов, но при этом не пострадает читаемость кода, т.к. каждая функция будет отвечать на свой вопрос. Может ответ на этот простой вопрос задаст вектор движения для меня в плане рассуждении о типах в будущем.
P.s.: я знаю что можно возвращать несколько типов начиная с php версии 8..+, будем условно считать что речь идёт о пхп версий ниже.
  • Вопрос задан
  • 494 просмотра
Решения вопроса 1
ipatiev
@ipatiev Куратор тега PHP
Потомок старинного рода Ипатьевых-Колотитьевых
Хорошие вопросы.
Разумеется, увеличивать количество запросов к БД ради красивой типизации - это абсолютно не вариант.

Как можно увидеть, в приведенной цитате написано не однозначно, а "скорее всего".
И поэтому надо конечно стараться, чтобы функция возвращала какое-то одно значение, но не делать этого любой ценой. Поэтому getUserDataFromDatabase вполне может возвращать array|false, а getUserIdFromDatabase - int|false, хотя я бы с ней не заморочивался, первой вполне достаточно. А userIsPresentInDb вообще не нужна, вместо неё можно использовать одну их предыдущих.

Другое дело, что нужного результата можно добиться и другим способом.
Ведь вполне можно вернуть пустой массив. И таким образом формально соблюсти типизацию.

return $result ?: []; // дёшево и сердито

Но это будет всё равно не очень красиво. В одной стороны - да, для проверки, вернула ли функция непустое значение, такой вариант вполне сгодится. Но с другой стороны все равно как-то неаккуратненько. Ведь мы на самом деле ждём не абы какой массив, а вполне определённый, с конкретным набором полей. То есть, если эта функция вернет массив вида [0, 42, 100500], то это будет явно не то, что нам нужно, но при этом типизация и слова поперёк не скажет.

Если думать от типизации, то функция getUserDataFromDatabase, возвращающая какой-то абстрактный массив - это нонсенс, бессмыслица. Эта функция должна возвращать юзера. Поэтому в идеале надо придумать способ указать, что функция возвращает не абстрактный, а конкретный массив определенного формата. Тем более, что такой способ как раз есть, ведь типизованный массив - это же объект! И для данного случая даже специальный паттерн есть - ValueObject (хотя в случае с пользователем лучше будет все-таки делать полноценный класс, содержащий не только данные, но и методы, например auth(), который сравнивает хэш пароля с введенным).

Соответственно, в идеале функция должна возвращать объект, представляющий пользователя.

И уже в этом объекте поле id может либо либо быть нулём, либо иметь какое-то положительное значение. Или даже объект может содержать отдельное свойство, заполнен/не заполнен.

И вот в этом случае типизация заиграет совсем новыми красками, и будет использоваться на 100%
function getUserFromDb( int $userId ) : UserObject {
        return $result ? UserObject::fillFromArray($result) : new UserObject();
};
$user = getUserFromDb();
if (!$user->id) {
    // нинашли :'(
}


Но опять же, как пишет ниже Сергей delphinpro, зависит от задачи.
И решив проблему правильной типизации значения, которое функция вернет, если нашла пользователя, можно вернуться к вопросу о том, что возвращать, если функция ничего не нашла. И в этом случае вполне подходящим вариантом будет вернуть null
function getUserFromDb( int $userId ) : ?UserObject {
    return $result ? UserObject::fillFromArray($result) : null;
};

if ($user = getUserFromDb()) {
    // нашли :)
}
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы