Проверка непустоты текста в ПХП: программисты пишут

Недавно я писал о проверке непустоты текста в ПХП. Конечно, суровые программисты подняли меня на смех: да если у тебя код так написан, что ты не знаешь, какого типа у тебя переменная, то чего от тебя вообще ждать. Стоит отметить, что среди моих знакомых есть несколько очень сильных программистов. Разумеется, никому из них не пришло в голову высказываться в подобном ключе. Крутые программисты знают, что нет смысла критиковать решение, пока не знаешь подробности задачи.

Напомню, я предложил проверять так:

if ((string) @$text !== '') { ... }

Мне понесли варианты. «Специально для этого есть функция empty ()»:

1

if (!empty ($text)) { ... }

Это код с ошибкой: для (int) 0 проверка будет пройдена.

2

if (trim ($text) !== '') { ... }

Это код с двумя ошибками: если переменная $text не определена, выведется предупреждение; если текст состоит из пробелов, проверка не будет пройдена.

3

if (isset ($text) && trim ($text)) { ... }

В этом коде исправлена только первая ошибка предыдущего.

Кстати, этот код полагается на «ленивое» вычисление логических выражений в ПХП: если просто поменять операнды конъюнкции местами, код снова станет с двумя ошибками. Само по себе это окей, но странно, когда такое предлагают программисты, борющиеся за чистоту кода.

4

if (isset ($text) && ($text !== "")) { ... }

Это длинный и мусорный код, и снова с ошибкой: и для false, и для NULL проверка будет пройдена.

5

if (isset ($text) && (string) $text !== '') { ... }

Этот код — длинная и мусорная версия моего. В нём просто соблюдён религиозный ритуал «не использовать оператор @», ну и он дополнительно полагается на ленивое вычисление логики.

Интересно, что никто из советчиков не указал на ошибку в моём исходном варианте (я её увидел сам, пока смотрел на предложенные альтернативы): если в переменную попадёт массив, проверка сработает, потому, что (string) от любого массива — это "Array". В зависимости от того, что дальше делать с этой переменной, это может привести к проблемам. В этом удлинённом варианте ошибка та же.

6

Все варианты кода с оператором ?? — с фатальной ошибкой: такого оператора нет в ПХП 5.4, на котором должна работать Эгея.

7

if (@strlen ($text)) { ... }

Этот код работает правильно.

Неприятность в том, что глядя на него нельзя этого сказать с уверенностью. Нужно читать документацию. Оказывается, у strlen () есть полезная особенность: если передать ему массив, он вернёт 0, а вовсе не 5 ("Array"). Правда, это только начиная с ПХП 5.2, хе-хе. В документации не сказано, как он ведёт с себя с переменными других типов, поэтому нужно идти дальше и читать документацию по неявному преобразованию типов, чтобы узнать, что strlen (0) вернёт 1, как мне и требуется. Полагаться на такие штуки, когда ожидаешь какого-то чёткого и тонкого поведения, как-то некомфортно.

Поэтому я пока оставил свой вариант, забив на потенциальную проблему с "Array".

Дальше
13 комментариев
Mopsicus 2019

Заголовок «Проверка непустоты текста в ПХП..» и далее «...если в переменную попадёт массив..»? Это как?)
Все эти языки со слабой типизацией до добра не доведут

Илья Бирман 2019

И снова ценный комментарий!

Alex 2019

Тоже к первой заметке хотел добавить про empty, но потом подумал ещё раз. А обязательно решение должно быть однострочником? Я так подумал из-за фраз вроде «длинная и мусорная версия», но уверенности нет. Напиши нормальное решение в несколько строк. Вынеси в функцию, если код переиспользуется или глаза мозолит.

P.S. Понимаю, бизнес-требования и всё такое, но php5.4 это уже прямо совсем плохо. В следующей мажорной версии Эгеи можно хотя бы до 5.6 требования поднять. Такая себе политика «не запустения» — хочешь новую версию, поднапрягись и установи обновленный софт (работает быстрее, есть саппорт). Невозможно? Используй старую версию Эгеи. Довольно гибко. При этом, исправления критических ошибок можно делать минорными версиями для всех веток.

Mopsicus 2019

И снова ценный комментарий!

Так а что удивляться?) Какой вопрос был, такие ответы и получил. Какое-то накидывание, чтобы посмотреть что из этого выйдет.
Заметка о том, что нет смысла критиковать решение, пока не знаешь подробности задачи — похоже на отмазку. Ну написал бы подробности сразу..

Илья Бирман 2019

Какие подробности? Что надо, чтобы не глючило? :-) Простите, не догадался внести такое требование в ТЗ!

Евгений Степанищев 2019

Не использование собаки («@») — не религия. Ты отключаешь механизм, который в нормальной ситуации приносит много пользы — позволяет находить ошибки.

Илья Бирман 2019

В чём преимущество кода:

$text = false;
if (is_file ('x.txt')) {
  $text = file_get_contents ('x.txt');
}

Над кодом:

$text = @file_get_contents ('x.txt');

?

Я вижу только недостатки: первый код длиннее при том же смысле, структурно сложнее и содержит потенциальные ошибки (например, если файл есть, но скрипту не хватает прав доступа).

Vadim Yegorov 2019

а почему полагаться на ленивое вычисление — плохо?

Илья Бирман 2019

Для меня норм. Для меня вообще норм использовать язык по назначению: нестрогую типизацию, подавление ошибок и другие классные фичи.

Алексей Солодкий 2019

Если уже речь зашла о типах (array) то стоит заметить что bool также будет давать неверные результаты.
if (@strlen ($text)) для $text === true будет истинным.

Решение для всех типов:
if (isset($text) && is_string($text) && $text !== ’’) {}

По поводу длинного и мусроного. Так конечно никто не пишет. Потому что обычно дело происходит внутри функции где уже:

  1. гарантируется что переменная $text существует, что снимает isset($text)
  2. гарантируется что $text это string через типизацию, что снимает is_string($text)
    Остаётся простой if ($text !== ’’) {}
Alexey Lebedev 2019

Мне кажется, что у тебя неточность в исходной статье. Ты говоришь о числе 0, но странно было бы, если бы оно туда попадало без активных действий с твоей стороны. Скорее ты столкнулся с тем, что в PHP строка ’0’ — это тоже falsy value. То есть переменная либо не определена, либо является строкой. Это выглядит более разумно.

Если же ты имел в виду именно проверку произвольных типов, то ты крайне неясно выразил эту мысль. И тут не может быть однозначного ответа без детального понимания контекста. Например, что делать с объектами, у которых определен метод __toString(), возвращающий пустую стоку?

Знаю, что на код ревью точно попросил бы переделать (string) @$text !== ’’. Не из-за предвзятости к собакам или приведению типов, а за-за того, что непонятно намерение программиста. Пришлось бы читать код выше и разбираться, какого типа значения могут сюда попадать и с какой целью.

Илья Бирман 2019

Да, это понятно. Части кода Эгеи — 15 лет. Я не могу просто всё мгновенно переписать так, чтобы гарантировать, что переменная будет точно какого-то типа или точно не будет содержать каких-то значений. Поэтому мне нужно работать с тем что есть, постепенно обновляя разные части и пиша их более чисто. При этом в отдельных местах, конечно, я понимаю, что переменная точно будет определена или точно не окажется массивом. Но как раз из соображений чистоты мне удобнее придумать универсальную проверку, просто как иероглиф такой, чтобы она везде в коде выглядела одинаково и работала безошибочно.

Егор Пономарев 2019

Как борец за чистоту кода могу предложить вынести проверку во вспомогательный метод с говорящим названием, например isEmpty(text). Тогда внутри можно будет написать сколь угодно некрасивый код, переиспользовать его в куче мест (меняя при этом только в одном), а также избавиться от проблемы «Неприятность в том, что глядя на него нельзя этого сказать с уверенностью».

Евгений Степанищев 2019

Не знаю как тебе тут ответить на твой комментарий, но тот и другой код — плохой. По разным причинам. Надеюсь как-нибудь пересечёмся и поговорим про это, в комментариях я что-то не готов :)

Илья Бирман 2019

Ты можешь «хорошо» изложить на ПХП мысль «прочитай содержимое файла в переменную, если получится, а если нет, то и хрен с ним»?

Konstantin Baryshnikov 2019

С @file_get_contents все нормально. Он сработает правильно. А вот в варианте с is_file между is_file и file_get_contents может случиться что угодно, ведь у нас многозадачная ОС.

Единственный недостаток — неявность намерения «если нет, то и хрен с ним».

А вот @$var — это сразу напрягает по двум причинам:
1) в нормально структурированном коде всегда известно, определена переменная или нет,
2) опечатка в имени переменной останется незамеченной,
3) если этот прием используется сплошь и рядом, оверхед от постоянно вызываемого механизма обработки ошибок может оказаться значительным (особенно если используется set_error_handler).

Konstantin Baryshnikov 2019

Оказывается, я не умею считать до трех, простите.

Евгений Степанищев 2019

Я бы сделал так:

<code>
function readFileOrDissmiss(string $filename): ?string
{
try {
$file = new \SplFileObject($filename);
$file->flock(LOCK_SH);

$content = $file->fread($file->getSize());
} catch (\RuntimeException $e) {
$content = null;
}

return $content;
}
</code>

Роман Жариков 2019

Для меня вообще норм использовать язык по назначению: нестрогую типизацию, подавление ошибок и другие классные фичи.

А не от нестрогой ли типизации, в итоге, возникла проблема в написании сейчас «универсального костыля» проверки, вместо простого использования объявления типов (аргументы функции) изначально (аналогично, подсказки для типов в PHP 5)?

Даже если в 5й версии для string отсутствует объявление типа, все равно это не аргумент в глобальной перспективе оставаться на php5, особенно если ядро проекта развивается дальше. В конечном итоге проверить тип в этой версии поможет gettype() или is_*()

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

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

Мои книги