Проверка непустоты текста в ПХП: программисты пишут
Недавно я писал о проверке непустоты текста в ПХП. Конечно, суровые программисты подняли меня на смех: да если у тебя код так написан, что ты не знаешь, какого типа у тебя переменная, то чего от тебя вообще ждать. Стоит отметить, что среди моих знакомых есть несколько очень сильных программистов. Разумеется, никому из них не пришло в голову высказываться в подобном ключе. Крутые программисты знают, что нет смысла критиковать решение, пока не знаешь подробности задачи.
Напомню, я предложил проверять так:
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".
Заголовок «Проверка непустоты текста в ПХП..» и далее «...если в переменную попадёт массив..»? Это как?)
Все эти языки со слабой типизацией до добра не доведут
И снова ценный комментарий!
Тоже к первой заметке хотел добавить про empty, но потом подумал ещё раз. А обязательно решение должно быть однострочником? Я так подумал из-за фраз вроде «длинная и мусорная версия», но уверенности нет. Напиши нормальное решение в несколько строк. Вынеси в функцию, если код переиспользуется или глаза мозолит.
P.S. Понимаю, бизнес-требования и всё такое, но php5.4 это уже прямо совсем плохо. В следующей мажорной версии Эгеи можно хотя бы до 5.6 требования поднять. Такая себе политика «не запустения» — хочешь новую версию, поднапрягись и установи обновленный софт (работает быстрее, есть саппорт). Невозможно? Используй старую версию Эгеи. Довольно гибко. При этом, исправления критических ошибок можно делать минорными версиями для всех веток.
Так а что удивляться?) Какой вопрос был, такие ответы и получил. Какое-то накидывание, чтобы посмотреть что из этого выйдет.
Заметка о том, что нет смысла критиковать решение, пока не знаешь подробности задачи — похоже на отмазку. Ну написал бы подробности сразу..
Какие подробности? Что надо, чтобы не глючило? :-) Простите, не догадался внести такое требование в ТЗ!
Не использование собаки («@») — не религия. Ты отключаешь механизм, который в нормальной ситуации приносит много пользы — позволяет находить ошибки.
В чём преимущество кода:
Над кодом:
?
Я вижу только недостатки: первый код длиннее при том же смысле, структурно сложнее и содержит потенциальные ошибки (например, если файл есть, но скрипту не хватает прав доступа).
а почему полагаться на ленивое вычисление — плохо?
Для меня норм. Для меня вообще норм использовать язык по назначению: нестрогую типизацию, подавление ошибок и другие классные фичи.
Если уже речь зашла о типах (array) то стоит заметить что bool также будет давать неверные результаты.
if (@strlen ($text)) для $text === true будет истинным.
Решение для всех типов:
if (isset($text) && is_string($text) && $text !== ’’) {}
По поводу длинного и мусроного. Так конечно никто не пишет. Потому что обычно дело происходит внутри функции где уже:
Остаётся простой if ($text !== ’’) {}
Мне кажется, что у тебя неточность в исходной статье. Ты говоришь о числе 0, но странно было бы, если бы оно туда попадало без активных действий с твоей стороны. Скорее ты столкнулся с тем, что в PHP строка ’0’ — это тоже falsy value. То есть переменная либо не определена, либо является строкой. Это выглядит более разумно.
Если же ты имел в виду именно проверку произвольных типов, то ты крайне неясно выразил эту мысль. И тут не может быть однозначного ответа без детального понимания контекста. Например, что делать с объектами, у которых определен метод __toString(), возвращающий пустую стоку?
Знаю, что на код ревью точно попросил бы переделать (string) @$text !== ’’. Не из-за предвзятости к собакам или приведению типов, а за-за того, что непонятно намерение программиста. Пришлось бы читать код выше и разбираться, какого типа значения могут сюда попадать и с какой целью.
Да, это понятно. Части кода Эгеи — 15 лет. Я не могу просто всё мгновенно переписать так, чтобы гарантировать, что переменная будет точно какого-то типа или точно не будет содержать каких-то значений. Поэтому мне нужно работать с тем что есть, постепенно обновляя разные части и пиша их более чисто. При этом в отдельных местах, конечно, я понимаю, что переменная точно будет определена или точно не окажется массивом. Но как раз из соображений чистоты мне удобнее придумать универсальную проверку, просто как иероглиф такой, чтобы она везде в коде выглядела одинаково и работала безошибочно.
Как борец за чистоту кода могу предложить вынести проверку во вспомогательный метод с говорящим названием, например isEmpty(text). Тогда внутри можно будет написать сколь угодно некрасивый код, переиспользовать его в куче мест (меняя при этом только в одном), а также избавиться от проблемы «Неприятность в том, что глядя на него нельзя этого сказать с уверенностью».
Не знаю как тебе тут ответить на твой комментарий, но тот и другой код — плохой. По разным причинам. Надеюсь как-нибудь пересечёмся и поговорим про это, в комментариях я что-то не готов :)
Ты можешь «хорошо» изложить на ПХП мысль «прочитай содержимое файла в переменную, если получится, а если нет, то и хрен с ним»?
С @file_get_contents все нормально. Он сработает правильно. А вот в варианте с is_file между is_file и file_get_contents может случиться что угодно, ведь у нас многозадачная ОС.
Единственный недостаток — неявность намерения «если нет, то и хрен с ним».
А вот @$var — это сразу напрягает по двум причинам:
1) в нормально структурированном коде всегда известно, определена переменная или нет,
2) опечатка в имени переменной останется незамеченной,
3) если этот прием используется сплошь и рядом, оверхед от постоянно вызываемого механизма обработки ошибок может оказаться значительным (особенно если используется set_error_handler).
Оказывается, я не умею считать до трех, простите.
Я бы сделал так:
<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>
А не от нестрогой ли типизации, в итоге, возникла проблема в написании сейчас «универсального костыля» проверки, вместо простого использования объявления типов (аргументы функции) изначально (аналогично, подсказки для типов в PHP 5)?
Даже если в 5й версии для string отсутствует объявление типа, все равно это не аргумент в глобальной перспективе оставаться на php5, особенно если ядро проекта развивается дальше. В конечном итоге проверить тип в этой версии поможет gettype() или is_*()
Ведь это большая головная боль потом, отслеживать возможные появления такого типа ошибок в программе, которые нарушают изначальную идею и появление более серьёзных багов (такой же аргумент касается и «классной фичи» про подавление ошибок).
Если уж так хочется без появлении ошибок, то когда при попытки передать в функцию значение несоответствующего типа будет выброшено исключение, его можно будет обработать, хотя бы, как минимум.