Pull to refresh

Comments 175

О каких «подводных камнях» речь, если вы сами приводите ссылку на мануал, где всё чётко описано про сравнение переменных и о неявном приведении типов. Как говорится, RTFM.
1. Не все изучали php по официальной документации. Есть книги более простые для понимания.
2. Не все изучали php. Если знаком с другими ЯП, то с php сможешь разобраться без rtfm.
1. Ну, таких людей нельзя называть программистами
2. Э… что? Может, все таки, нельзя без мануала, раз для таких простейших вещей посты создаются?
1. Что ж поделать… Мир не идеален.
2. По результатам голосования, на данный момент, для 123 человек этот топик оказался полезным.
Я так понимаю, что ни nokimaro ни nick4fake никаких дополнительных статей или книжек об используемых ими инструментах не читают?! Штудируют исключительно один лишь мануал в надежде, что не упустят никакой детали, правильно истолкуют каждое слово и безошибочно соотнесут все части мануала между собой?!
Что за ерунда? Откуда такие выводы?
Я почему-то расценил Ваш комментарий как пренебрежение к чтению какой-либо литературы, кроме мануала.
Не вижу ни малейшей связи.

— Может, все таки, нельзя без мануала
Где тут пренебрежение к другой литературе?
Полная цитата: «Может, все таки, нельзя без мануала, раз для таких простейших вещей посты создаются?»
В моём понимании это также означает, что «мануал нужен, чтобы не создавать посты для простейших вещей» и, как следствие, «если что-то есть в мануале, то статьи, раскрывающие какую-то тему более подробно, не нужны».
Для любого программиста очень важно разбираться в таких понятиях как необходимое и достаточное условия. Ваше неправильное первого комментария (http://habrahabr.ru/post/190440/#comment_6613804), говорит, что вы «плаваете» в этой теме.

Суть того комментария: для того чтоб быть программистом надо читать мануалы. Это необходимое, но не достаточное условие.
Понимание — это относительное понятие, поэтому не стоит выдавать собственную версию понимания за абсолютную истину. Как тот комментарий привёл меня к моему пониманию его смысла я уже описал. Ещё раз: во фразе «нельзя без мануала, раз для таких простейших вещей посты создаются» я рассмотрел противопоставление чтения мануала чтению дополнительных статей, на основании которого я и пришёл к выводу о пренебрежительном отношении автора комментария к чтению дополнительных статей (замечу, что автор опроверг мою догадку).
Мануал — истина в первой инстанции, а об особенностях все давно написано в комментариях к мануалу. Если вы встретили что-то совсем странное и необъяснимое — этого с очень высокой вероятностью нет в книгах, а если и есть, то фиг найдете среди сотен тех самых книг. Да и по-настоящему опытные программеры книг почти никогда не пишут — им это не нужно, они и без книг зарабатывают прилично.
Основы и особенности PHP прекрасно и чаще всего в полной мере описаны в мануале (в английской версии). Для этого не нужны книги и статьи, которые по сути дублируют мануал.
Если вы переходите с другого языка — потрудитесь почитать основы нового языка. Там покрыты все основные особенности и возможности языка. Более того — это бесплатно и вам точно не наврут, как это бывает в книгах.
По сути всю статью можно было уместить в ссылку на мануал про типы данных, их приведение и сравнение + добавить строчку «Внимательно читайте что возвращает функция и ознакомьтесь с примерами».
Да пребудет с вами Разум.
> Мануал — истина в первой инстанции

Истина в первой инстанции — это код, а не мануал.

> Да и по-настоящему опытные программеры книг почти никогда не пишут — им это не нужно, они и без книг зарабатывают прилично.

У Вас слишком меркантильное видение мира.

> Если вы переходите с другого языка — потрудитесь почитать основы нового языка. Там покрыты все основные особенности и возможности языка.

Вы удивитесь, но в данном посте говорится не об основной особенности (впрочем, кто-то может считать иначе).

> По сути всю статью можно было уместить в ссылку на мануал про типы данных, их приведение и сравнение + добавить строчку «Внимательно читайте что возвращает функция и ознакомьтесь с примерами»

А можно, наоборот, разворачивать свою мысль более полно, без игр в шарады.
я показывал "===" C# программистам, они были в диких непонятках. Без понимания что код делает, его сложно понять, т.е. нужно идти и rtfm. А в мануале описаны основы и правила по которым что-то работает и как правило все работает как описано (что-то не припомню особых фейков). Да и при желании на пхп можно написать код, который вынесет мозг кому угодно, но будет работать как требовалось =) Код не может объяснить как конкретно он работает в разных случаях. Тут нужно все тестить. Или просто прочитать мануал =) Да, там могут не оговариваться дикие особенности, но после понимания что есть разница между == и ===, программист должен проклятсь нестрогую типизацию и начать думать как сравнивать правильно в том или ином случае.

В посте, конечно, приводятся примеры крайне неожиданные, хотя ни один из них я за 5 лет использования PHP не словил. Больше проблем было из-за вот этих табличек: PHP сравнения, пока не выучил =)

И вообще: PHP — отвратительный язык, с кучей граблей и т.п. Не используйте его! ;-)
PS: мечтаю о введении возможности указывать тип данных переменным
Под истиной в первой инстанции я имел ввиду код интерпретатора PHP.
ух блин… ну это, конечно, верно, хотя там наверняка черт ногу сломит =) Я все же предпочту поверить в мануал и магию =)
При условии, что код согласован с документацией, а то это может быть баг, который в следующем минорном релизе пофиксят.
Документация отображает лишь желаемое поведение. Реальное поведение отображает код. Поэтому, если в документации говорится одно, а код ведет себя иначе, то подстраиваться всё равно придётся именно под поведение кода (в случае с багом, подстраиваться придётся до момента выхода новой версии кода). Поэтому документация всё равно будет вторичным источником информации.
Если они (реальное и желаемое) совпадают, то можно смело использовать. Если нет, то я предпочту какой-нибудь обходной путь, костыль или велосипед, чем использовать фичу, которая может быть багом.
Об этом я и говорил: если в документации говорится одно, а код ведёт себя по другому, то писать программу в соответствии с документацией будет нельзя, необходимо будет искать обходные пути.
Но и в соответствии с кодом её писать опасно.
На текущий момент времени (с текущей версией кода) написание в соответствии с кодом является самым безопасным. Опасность представляет получение новой версии кода без просмотра изменений. Ведь даже, если в документации напишут, что баг исправлен, то при получении новой версии кода всё равно надо будет проверить, действительно ли баг исправлен и код теперь ведёт себя так, как следует.

Т.е. документация, по сути, является не продуктом, который будет использоваться непосредственно, а лишь пояснением к нему.
P.S.: я не имел ввиду, что документацию не надо учитывать (в ней всё же записаны мысли и желания разработчиков относительно текущего и будущего состояния продукта), говорил лишь о первичности и вторичности в единичный момент времени.
Если документация и код различаются — добро пожаловать в багтрекер, там вам скажут, чему верить.
А Вы сами не можете определиться чему верить? Или у Вас операционная система исполняет не только код, но и документацию?
Если документация и транслятор ЯП расходятся во мнении, то не стоит полагаться ни на кого, так как в следующих релизах могут пофиксить что-то одно.
Однако, при разработке сайтов, полагаются именно на поведение кода браузера. И полагаются от безысходности, а не от собственного желания.
P.S.: в следующих релизах могут изменить даже то, что закреплено в текущей версии документации.
Я при разработке сайтов (сервер-сайд кода) полагаюсь и на документацию, и на реальное поведение. В случае противоречия просто леплю свой костыль.
но после понимания что есть разница между == и ===, программист должен проклятсь нестрогую типизацию и начать думать как сравнивать правильно в том или ином случае.

Слабая типизация бывает весьма удобна. Портят её только вот такие дикие нюансы, что ' 7' == 7, а '7 ' != 7. Или что строка '0' приводится к false, а строка 'false' — нет.
Ну, всё же, существующее положение вещей более логично (хоть и менее логично, чем должно было бы быть), чем предлагаете вы.
«Подводные камни» в неочевидности подобного поведения и в разнообразии возможных вариантов записи чисел, распознаваемых в строке (шестнадцатеричные, со знаком и т.п.). А в мануале этому моменту уделено лишь несколько слов в абзаце и две строчки в примере с кодом.
// Использование функции "strcmp"
strcmp($aaa, $bbb) == 0;

Хех, советуете "=="? ;-)

php > var_dump(strcmp("hello", array(123)));
Warning: strcmp() expects parameter 2 to be string, array given in eval()''d code on line 1
NULL

php > var_dump(NULL == 0);
true


Эксплуатируется через query string ?param[]=123
Хех, советуете "=="? ;-)… php > var_dump(NULL == 0);

Да, не знал этого.

Эксплуатируется через query string ?param[]=123

Это да, за входными параметрами в этом случае надо следить. :)
Тоже сразу же вспомнилось, хотел написать :) Почему то первая ассоциация на strcmp
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
> После десериализации (json_decode) числа могут быть представлены в виде строк
Пруф, пожалуйста.

php > var_dump(json_decode('{"a": 123, "b": "123"}'));
object(stdClass)#1 (2) {
  ["a"]=> int(123)
  ["b"]=> string(3) "123"
}
php > var_dump(json_decode('{"number": 12345678901234567890}', false, 512, JSON_BIGINT_AS_STRING));
class stdClass#1 (1) {
  public $number =>
  string(20) "12345678901234567890"
}

Или вообще может быть
php > var_dump(json_decode('{«number»: 12345678901234567890}'));
class stdClass#1 (1) {
public $number =>
double(1.2345678901235E+19)
}

Ок, можем получить строку, если самому сказать, что нужна строка. Но дело вот в чем:

php > var_dump(json_decode('{"n": 12345678901234567890}')->n === 12345678901234567890);
bool(true)

php > var_dump(json_decode('{"n": 12345678901234567890}', false, 512, JSON_BIGINT_AS_STRING)->n === 12345678901234567890);
bool(false)


Т.е. === работает нормально и для больших чисел и для маленьких, если намеренно не просить json_decode вернуть неправду.

PS. Оказывается php 5.5 сломали interactive mode, ппц.
Иногда большие числа требуется обрабатывать точно, а не с двойной точностью.
Вопрос не в том, зачем использовать JSON_BIGINT_AS_STRING. Вопрос в том, что если вы изначально знаете, что число вернётся строкой, зачем вам строго сравнивать эту строку с числом?
А самое смешное забыли:

php> var_dump(7 == '7_nabor_bukav');
bool(true)
Нет, как раз в случае, когда один из операндов имеет числовой тип, неочевидного поведения нет. Я писал о случае, когда оба операнда являются строками и перед тем, как сравнить их как строки, PHP пытается определить похожи ли эти строки на числа и если да, то попробовать сначала сравнить их как числа, а уж затем как строки.
Видимо разные люди по-разному воспринимают «очевидность» и «похожесть».
Говоря, что «неочевидного поведения нет» я просто подразумевал другую неочевидность (из-за чего некорректно обобщил своё высказывание на Ваш пример).

Неочевидность в Вашем примере заключается, видимо, в том, что не числовой тип приводится к строковому, а строковой к числовому. И если учесть, что допустимый набор символов числового типа является подмножеством допустимого набора символов строкового типа (а не наоборот), то неочевидность поведения здесь, конечно же, тоже есть. Просто, повторюсь, я говорил о другом.
Пожалуйста:

var_dump('7' == '7_nabor_bukav'); // false

Тот же самый пример, но первый операнд в кавычках.

Касательно первого предложения в вашей статье:

Из-за того, что в PHP при сравнении строк оператор "==" пытается сначала преобразовать их в числа [1][2] (даже, если оба операнда — строки).

Получается '7' (string) преобразовывается не в 7 (int). О каких вообще «числах» речь идет?

Далее по одному из ваших примеров:

var_dump('123' == '       123'); // true
var_dump('123' == '123       '); // false

Это я вам к тому, что тема не раскрыта полно.
Это я вам к тому, что тема не раскрыта полно.

Так и есть. Я рассматривал лишь ситуацию, когда результат сравнения двух строковых переменных с разными значениями является положительным.
У меня такое ощущение, что вы — тролль, но сами об этом не знаете =)
Ах да, передаю привет тому, кто насрал мне в карму!
У меня такое ощущение, что вы — тролль, но сами об этом не знаете =)

Вы заставили меня задуматься. :)
Это нормальная ситуация и не только в php.
Нормально — это когда все происходит явно. Я тут ничего нормального не вижу. Я понимаю, что так исторически сложилось. Но если бы вы взялись разрабатывать новый язык программирования, вы бы так же сделали?
Я бы сделал также, потому что это выглядит более чем нормально в контексте нестрого типизированных языков программирования, каким и является PHP.
Попробуйте сами придумать правила для нестрогого сравнения и Вам станет очевидно, что разработчики языка PHP умные люди.
Выполните в JavaScript код 7 == '7_nabor_bukav' и результат будет противоположный результату в PHP (а ведь в JavaScript при использовании == тоже происходит приведение типов).

P.S.: выше я уже писал, что подобное поведение кажется неочевидным из-за того, что в PHP при приведении типов, тип с большим допустимым набором символов приводится к типу с меньшим (а не наоборот), из-за чего значимые символы как бы теряются.
И что? Вы хотите мне рассказать, что JavaScript и PHP имеют различия? Так это вроде очевидно. А попробуйте проделать эти сравнения в Cache и может появится третья версия нестрогого сравнения и какая из них «правильная»? Мир разработки настолько разнообразен, что место находится для многих концепций.
Если вышеуказанный пример вам кажется нормальным, приведите пример полезного использования такого поведения и я с вами соглашусь.
> И что?
Из моего комментария ничего сделовать и не должно. Это был ответ на Ваш комментарий о том, что «это выглядит более чем нормально в контексте нестрого типизированных языков». Как видите, некоторые нестрого типизированные языки не считают такой подход нормальным.
Согласитесь, что если не читать документацию по языку Javascript, то и в нём можно найти достаточно много «подводных» камней?
По крайней мере, я не стал бы делать неявного приведения типов с потерей данных. А лучше вообще для разных типов возвращать false, как это сделано в пайтоне. У нас же определены типы на момент выполнения операции.
А кроме Python'а на каких еще языках Вы пишите?
Хех. Звучит как: «А ты с какого района?».
С какой целью интересуетесь?
Думаю, что с той целью, что достаточно странно утверждать «лучше вообще… false». Почему лучше? Кто так сказал? Где это ещё реализовано именно таким способом?

Зачем вообще нужно строгое сравнение, если для разных типов нестрогое возвращает false вне зависимости от равенства приведённых значений?
Приводить можно int к float, float к double и т.п. без потери данных. В остальных случаях неявное преобразование типа вообще не нужно, разве что для костылирования. Зачем сравнивать строку с числом? — Чтобы всех запутать.

Типа: программисту будет лениво приводить полученную из БД строку к числу — давайте сделаем это за него абы как.

Нормальная реализация, например, в Ruby, С++. В C++ и Java этот оператор вообще перегружается если надо. Скажите лучше, в каких еще языках есть == и === в том смысле, в котором это существует в PHP и JS?
На phpsadness.com была ссылка на багу, в которой такое поведение объяснялось желанием разработчиков подстроить PHP под работу с HTTP и базами данных (данные из которых приходят в виде строк) для упрощения разработки. Ниже оригинальные комментарии.

От jabakobob at gmail dot com:
The conversion to a number is necessary because programmers don't differentiate
between strings and numbers in PHP. Consider the following code:

if ($_GET[«a»] == $_GET[«b»]) echo «a is same as b!»;

The result will be the same if the query string is ?a=1&b=1 or ?a=1&b=1.0 or?
a=01&b=1 because PHP is loosely typed.

Internally $_GET[«a»] and $_GET[«b»] are both strings, but we can't do a string
comparison. If you want a string comparison, use strcmp.


От rasmus@php.net:
PHP has two sets of comparison operators as well. == and ===
They aren't numeric and string, they are loose and strict. In the majority of
cases when dealing with HTTP requests and database results, which is what PHP
deals with most, the loose comparison makes life easiest on the developer.
«Из-за того, что в PHP при сравнении строк оператор „==“ пытается сначала преобразовать их в числа (даже, если оба операнда — строки).»

В документации сказано, что при сравнении PHP преобразует строку в число только в том случае, если сравниваются строка и число. Это касается и оператора switch. К примеру:

var_dump(5 == 'Мой сосед вырыл себе яму'); // false
var_dump(5 == '5 чудаков вырыли себе яму'); // true

В первом случае будет false т.к. (int) 'Мой сосед вырыл себе яму' будет 0.
Во втором случае будет true т.к. (int) '5 чудаков вырыли себе яму' будет 5.
В документации сказано следующее:
If you compare a number with a string or the comparison involves numerical strings, then each string is converted to a number and the comparison performed numerically. These rules also apply to the switch statement. The type conversion does not take place when the comparison is === or !== as this involves comparing the type as well as the value.

И из русской версии:
В случае, если вы сравниваете число со строкой или две строки, содержащие числа, каждая строка будет преобразована в число, и сравниваться они будут как числа. Эти правила также распространяются на оператор switch. Преобразование типов не происходит при использовании === или !== так как в этом случае кроме самих значений сравниваются еще и типы.
Я чуть-чуть поправлю.

«If you compare a number with a string or the comparison involves numerical strings».
переводится как:
«Если сравнивать число со строкой или если сравнение включает в себя числовые строки».

Обратите внимание на фразу «числовые строки». То есть если строка содержит числа. По второй ссылке с русской документацией это как раз уточняется.
Я Вас не понимаю. Первый Ваш комментарий я расценил как утверждение о том, что в мануале не говорится о выполнении попытки сравнения двух строковых переменных как числовых переменных. Уточните, что именно Вы имели ввиду.
Не «строка содержит числа» (строка '7 ' содержит числа, но со второй строкой будет сравниваться как строка, ('7 ' == '7') === false, а, емнип, строка является числовой (is_numeric('7 ') === false, хотя ((integer)'7 ') === 7).
Я зашел сюда чтобы увидеть мемы со словами WAT и PHP.
Перепутали технический ресурс со своей развлекательной лентой ВКонтакте?
По-видимому, это вовсе и не WAT.
По-видимому, так и должно быть, и понятно, и удобно, и быстро, и все только радуются когда используют в своих проектах эту особенность интерпритатора.
Ага.

Имхо, критичное отношение — краеугольный камень разумности вообще. А юмор — это одно из его проявлений. Но это, в общем-то, не суть.
Это всё верно.
Беда только в том, что «мемы со словами WAT и PHP» имеют весьма отдалённое отношение как к юмору, так и к разумности.
Хабр, ты меня разочаровываешь, статья о PHP про разницу между == и === это уже перебор (да еще и в плюсе — это двойной перебор). Давайте напишем отдельно про < и > или про то что можно вместо or написать ||, а вместо and && — вдруг кто-то не знает еще?
Нет, вы зря так. Статья имхо очень хорошая. Заставляет задуматься.
Ну каждому пришло в голову что-то свое, судя по минусам. «Кто о чем», как говорится в известной поговорке. Я вот задумываюсь, например, в скольких местах такое поведение может потенциально вызвать дыры в безопасности в том коде, который я писал за последние 15 лет. Между «знать» и «разобрать по косточкам» — большая разница, и данный пост очень хорошо именно по косточкам все разбирает, я считаю.
Видимо мне повезло и еще в начале пути я узнал в чем отличия между == и ===, и чтобы не было недопонимания у php, использую или строки, или числа в явном виде (без извращений, как в примере), для bool обычно ===, хотя когда параметр может быть NULL, '','0', 0 или false, то как раз == очень удобно использовать. И не стоит забывать о всяких is_numeric, is_array и т.д.
> и чтобы не было недопонимания у php, использую или строки, или числа в явном виде (без извращений, как в примере)

Видимо, Вы так ничего и не поняли. Примеры в статье приведены для иллюстрации возможных ошибок при сравнении строк со строками, а не в качестве пропаганды использования числовых строк вместо чисел в явном виде.
Строки в примерах в большинстве своем числа, в том или ином виде, заключенные в ковычки.
В том-то и дело, что вид строго определен и '7 ' для php не число, а ' 7' — число (пробелы с разных строн). Вам очевидны правила, по которым php принимает такое решение?
Мне очевидно, что если на входе нечто странное и надо сравнить точно, то есть ===, а если надо чтобы php подумал сам что и как сравнить, то есть ==. Это вот такой вот язык и это надо принять как должное и знать при разработке приложений/сайтов/хомяков. И всегда можно пересобрать свой php (с вытекающими отовсюду последствиями) или использовать другой язык. ИМХО проблему на ровном месте устраивают. PHP это такой язык, где нечто приходит на вход (причем программист заранее должен знать число придет или строка или нечто, для чего надо ипользовать == или ===), [делает запрос к базе данных/кешу] и выводит результат пользователю. Не стоит усложнять простые вещи и искать как бы сделать так, чтобы php вывел что-то странное. Можно еще поругать php за ограниченность с int на 32 битных машинах, за то что файл больше 2гб не принимает или еще какие изъяны поискать и повыдумывать.
Специально помучал 7ку, при всех сравнениях у меня false. Только is_numeric(' 7') как число распознается и имхо правильно, к тому же ман никто не отменял и даже в русском варианте там внезапно описано: «Проверяет, является ли переменная числом или строкой, содержащей число». Далее уже вариации на тему, что вы конкретно ожидаете от входящих данных (float, int и т.д.).
А если кто-то не читает маны (хотя и сам грешу этим), то у меня плохие новости для таких программистов.
Проверяет, является ли переменная числом или строкой, содержащей число

В том-то и дело, что строка '7 ' содержит число, но как число не интерпретируется! И не всегда программист может знать, что придет на входе и рассчитывает на неявное приведение типов при нестрогом сравнении, но не настолько же неявном, что даже мануал ситуацию описывает неоднозначно.
Просто ради интереса, можете привести пример, когда программист не знает что пихнет юзер/источник. Просто обычно, в зависимости от входящих данных, что-то должно посчитаться или записаться, а если программист ждет целое число, то скорее всего в коде будет $var=(int)$_GET['user_value']; и эта проблема уже не будет волновать.
Любое строковое значение, которое PHP по своему усмотрению решит привести к числу, потому что оно на число похоже, а в мануале критерии похожести не описаны.
Прям вот сам PHP меняет в _GET/_POST/_другой_вариант_ввода_данных значения самопроизвольно? А потом прогер это пихает на обработку без проверок?
Да, ждем две строки, сравниваем их нестрого (зачем строго, если и так строки, в типе мы уверены, это же HTTP ) и получаем, что $_REQUEST['password'] == $_REQUEST['password_confirm'], хотя на самом деле они !==.
Я даже незнаю что сказать, вроде уже не раз сказал выше про вот это вот… Но как раз во это вот то и есть проблема программиста. Всё что приходит от юзера (или любого другого источника, которому вы не доверяете на 100%) надо проверять как можно строже. С таким же успехом можно просто оставить if (true) и не париться с проверками.
Пускай такая проверка:
if (gettype($_REQUEST['password']) == gettype($_REQUEST['password_confirm'])
    && $_REQUEST['password'] == $_REQUEST['password_confirm'])) { 
// ...
}

Читаем ман по сравнению и видим
$a == $b Equal TRUE if $a is equal to $b after type juggling.
$a === $b Identical TRUE if $a is equal to $b, and they are of the same type.

Вроде логически размышляя, эта проверка аналогична
if ($_REQUEST['password'] === $_REQUEST['password_confirm'])) { 
// ...
}

но есть нюанс, который невнятно описан дальше, и который не у всех откладывается в памяти даже после многократного прочтения. Вот сложно мне понять зачем решили в некоторых случаях две сравнимые строки приводить к числам для сравнения. Если надо сравнивать числа, то я приведу к числам, если строки, то и так строки пришли.
Честно говоря я не понимаю что сверху. Обе строки как string по идее придут и это всегда будет true, т.е. условие вообще бессмысленое, во всяком случае у меня вот такое приходит:
array(2) {
   ["pass"]=> string(1) "7" // просто 7
   ["pass_confirm"]=> string(2) " 7" // пробел 7
}

и при сравнении (=== конечно же):
bool(false)

На всякий случай даже хеши от пришедших значений, чтобы убедится, что php ничего не меняет сам по себе (иначе хеши бы были равны):
 string(32) "8f14e45fceea167a5a36dedd4bea2543"
 string(32) "6b958f957985dc14ee63064715e541f3" 
По ману === возвращает true, когда равны типы и значения, код сверху имитирует это поведение, но не полностью, как раз из-за сабжа статьи.
Я не понимаю зачем в реальном проекте подобные вещи использовать, если есть простое ===? Какая то сферическая проблема в вакууме выходит, для которой еще и кода в 2 раза больше написать надо.
Справедливости ради скажу что сам попадался на проблему с == и ===, когда функция возвращала FALSE или значение от 0 до N, соотвественно myfunc()==false истина, если функция возвращает (int)0. Но это опять же косяк именно программиста. В другом же случае == очень удобно, когда фреймворк или API возвращает объект/массив, а там числа в стрингах, все их перебирать и преобразовывать лениво было бы, если бы не подобное поведение PHP.
Это демонстрация интуитивной разницы между == и ===. Но == в некоторых случаях (is_numeric истинно для обоих операндов как минимум) производит ещё более нестрогую проверку, чем от него ожидаешь. Проблема PHP во многом в том, что в нём очень много таких нюансов, которые быстро забываются.

Ну а false == func() классика, на нем ошибаешься только один раз :)
alexshelkov, любые данные из поста. Например:

$_REQUEST['password'] = '0X1D';
$_REQUEST['password_confirm'] = '29E0';
Просто ради интереса, можете привести пример, когда программист не знает что пихнет юзер/источник.

Собственно, он никогда этого не знает. В качестве примера атаки через источник данных (который воспринимался как доверенный) можно вспомнить комплект из двух багов в phpMyAdmin, через которые он на ура ломался. Первый баг был банальный: с включенными register_globals можно было на время одного запроса докинуть свой mysql сервер в конфиг. Второй — уже интереснее: использование имени поля/таблицы (которые отдавал тот докинутый сервер mysql, фейковый, конечно же) в preg_replace с модификатором /e без надлежащего эскейпинга.
Собственно как раз знает, что придет SQL «строка» и мы снова упираемся в ошибку программиста, а не в то что php чего-то не так преобразовывает.
Видимо мне повезло и еще в начале пути я узнал в чем отличия между == и ===, и чтобы не было недопонимания у php, использую или строки, или числа в явном виде

Это вы только так думаете. :) Бьюсь об заклад, что из 100 случаев, когда вы пытаетесь применять эту идею на практике, вы в 10 ошибаетесь, просто не замечаете этого (но и ошибка эта не фатальна). Это как любители использовать табуляцию (а не пробелы) для отступов в коде: они пребывают в блаженной вере в то, что у них и правда везде табуляторы, до тех пор пока однажды не запускают редактор, в котором подсвечиваются пробельные символы и не замечают, что в куче мест табы и пробелы у них на самом деле смешались (ну или пока не запустят git и не получат от него предупреждения). (Нельзя просто так взять и использовать табы. Нужна подсветка со стороны редактора.)
По косточкам он всё же не разобрал. Проблему обозначил, решение в лоб привёл, но не объяснил. Насколько я помню, такое поведение возникает при сравнении двух строк, если они обе при вызове is_numeric() дают true. Исключений, по крайней мере не обнаружил в различных сочетаниях.
Чисто технически объяснение довольно простое: если для сравнения на равенство приведение не нужно, то для сравнения на больше-меньше оно в языке с нестрогой типизацией становится обязательным. А поскольку функция для для всех операций сравнения (больше/меньше/равно) только одна, возвращающая 1, -1 или 0, то и приведение делается для всех.
Почему функция одна — это другой вопрос. Думаю, для удобства разработчика языка.
Не о том речь. Обычно приведение осуществляется по типам операндов. Иногда имеют влияние несколько фиксированных значений, но тут неограниченное по большому счету множество строковых значений может «внезапно» привестись к целому, плюс простого мнемонического правила не существует, да даже регулярку с ходу не напишешь, не говоря о том, что документация не очень внятно объясняет «на пальцах».
Не понял, если честно, к какому комментарию эта реплика.
Я как раз объяснил — почему так происходит.

А воображаемые проблемы, связанные с таким поведением — это уже другой вопрос. На самом деле их важность сильно раздута. Для заведомых строк просто используем строгое сравнение, и не паримся.
В этом комментарии вы не объяснили зачем нужно иногда оба строковых операнда приводить к числу — пример про сравнение на больше-меньше '9' и '11'. Но лично мне кажется, что не стоило из-за этого усложнять и так не всегда понятное с ходу приведение типов в PHP.
Выше я приводил комментарии из баги на php.net где писали, что это было сделано для того, чтобы $_GET[«a»] == $_GET[«b»] как при ?a=1&b=1 так и ?a=1&b=1.0
Может хватит снобизмом заниматься? Статья не о том, что == приводит операнды к одному типу, а === нет, а о том, что == имеет неочевидное поведение в случае, когда операнды и так имеют одинаковый тип.
Да было уже несколько таких статей. Чего тему пережёвывать по сто раз-то?
1. Приводите ссылки на статьи (предварительно прочитав статьи, ссылки на которые Вы приводите).
2. Целью поста так же является сбор статистики (в посте присутствует опрос, если кто не заметил).
3. Если Вы где-то уже обсуждали эту тему в прошлом, то Вас никто не заставляет обсуждать её в данном посте.
1. Ссылки я вам привёл. Обсуждение поднятой вами темы: habrahabr.ru/post/26577/#comment_673049, были и ещё, просто мне не хочется время тратить и искать
2. я заметил, ну так и сделали бы пост-опрос
3. никто не заставляет, я и не жаловался.

просто одна и та же тема пережёвывается несколько раз. Зачем?
Кстати, вот мой же комментарий на эту тему к статье, которой несколько месяцев: habrahabr.ru/post/176117/#comment_6118539

Эта тема частенько всплывает на Хабре. Пора уже точку поставить.
Не то это. Там вы пишете о ситуации когда один из операндов в сравнении число (это во многих языках есть), а тут описывается ситуация, когда оба операнда строки, но PHP по своим соображениям приводит их оба к числам — вот это неочевидный момент. Да и документация не очень внятно его описывает.
Я вам уже присылал комментарий где в точности это и говорится: habrahabr.ru/post/26577/#comment_673049
дык, PHP кастует строку в число, используя ее до первого нечислового символа:
«123qwe» == 123

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

> 3. никто не заставляет, я и не жаловался.
Вы жаловались на то, что кто-то пережёвывает тему, которую Вы уже обсуждали ранее: «Да было уже несколько таких статей. Чего тему пережёвывать по сто раз-то?».
Некоторые на самом деле считают &&/and и ||/or взаимозаменяемыми. На чем и горят:

2 && 0 ? 'a' : 'b'      // b
2 and 0 ? 'a' : 'b'     // 1
Поправочка: 'b' и true соответственно
В контексте данного топика 1==true.
Прошу прощения, надо было мне уточнить, что речь про конструкции вида if($val1==123 && $val2==345). Вариант с expr? true: false не люблю, особенно когда кто-то еще и вложения понапихает — черт ногу сломит и хочется убивать. Старый* добрый вариант понятнее и однозначнее:
if (expr) {
   true
} else {
   false
}

* с книжек из детства про программирование на бейсике на клоне спектрума
А (boolean)(expr) чем не нравится?
Ну так в чем проблема? Давайте напишем свою функцию сравнения, которая будет устраивать нас полностью :)
Ткните меня носом в то место, где в этих статьях говорится о случае, описываемом в этом посте. Не нашёл этого когда просматривал эти статьи перед публикацией поста, не нашёл этого и сейчас.
Отвечу в комментарии выше.
Автор, раз Вы не читаете мануал, то вот Вам тема для следующей статьи:

$a = true and false;
var_dump($a); //true

$a = false or true;
var_dump($a); //false

Почему я не читаю мануал?! Читаю, даже ссылку в посте на мануал приводил. Вы не внимательно читали мой пост. А о приоритетах операторов, я уверен, Вы и сами сможете пост написать, если посчитаете эту тему важной для освещения.
Пониженный приоритет у and и or (также как и here-документы) были сделаны изначально с оглядкой на perl, где данные операторы применяются сплошь и рядом (и весьма удобны). Однако в php они практически бесполезны, потому что в php нельзя сделать, например, "...or return" или "… and break" (в перле — можно). То же самое и с here-документами: в перле и bash они позволяют как бы вставлять в некоторую точку внутри (!) выражения целый текст, оставляя само выражение при этом на одной и той же строке. В php же эта концепция не была понята, поэтому here-доументы в нем — просто разновидность строковой константы, хрупкая, неповоротливая.
Ну, конструкции типа $var = 0 or trigger_error('Notice ...'); встречаются.
Да практически нет. С trigger_error — плохой пример, потому что trigger_error ведет себя обычно ео-разному на боевом сервере и на разработческом. Пример получше — «or die», но он тоже редко применяется в силу понятных причин. А вот «or return», «or break», «or throw» — те, которые как раз и нужны, — не работают.
А можно пояснить, почему плохой? Сама-то функция ведет себя одинаково — кидает ошибку, на которую, в свою очередь, по-разному реагирует РНР — ради чего, собственно, мы эту функцию и используем. А редко она встречается исключительно от поголовной неграмотности пхпешных масс, которые как раз и лепят die(), там где надо кидать ошибку.

Но то, что language constructs не могут участвовать в логических выражениях — это да, неудобство.
С die/exit обычно не нужен пониженный приоритет операторов — умерла так умерла
Не-не-не, Девид Блейн!
Без пониженного оно будет возвращать булев результат логической операции, если не помрёт ;)
А, ну да, торможу. Не всегда же первая часть булева.
Ладно уж php. Недавно кверю написал в mysql консоли: update measures set tonality='C' where tonality=0; хорошо, что был бэкап недавний. Про == и === тема то довольно популярная.
90% кода который я встречал на php уязвимы только за счет одной этой особенности. За математикой дело не станет.
Работая в чужом коде на php знайте — вы уязвимы если при поиске по проекту найдется "=="(исключая "===")!
Это ж ужас какой-то… все всё ВСЁ сравнивается как строки при нестрогом сравнении. Нестрогое сравнение+нестрогая типизация = сами-знаете-что
Извините нет времени писать что-то еще… *срочно ушел рефакторить*
все всё ВСЁ сравнивается как строки при нестрогом сравнении

Статья как раз о том, что иногда даже строки не сравниваются как строки. Даже (string)$var1 == (string)$var2 может сравниваться как числа.
Все сравнивается, как строки. Кроме строк, содержащих числа.
… и строк, которые могут быть интерпретированы, как числа, «1e2», например.
… но только, если это целые числа, с плавающей запятой или шестнадцатеричные (но не восьмеричные или двоичные, т.к. 10 != '012' и 10 != '0b1010').
А что, строка, содержащая цифры перестает быть строкой?
При сравнении — да. Об этом же и весь сыр-бор :)
Весь сыр-бор из-за того, что пхп так думает, но на самом деле же нет.
Ну, у него нет вариантов.
Как я уже писал выше, функция сравнения всего одна — и на равенство и на больше/меньше. И если для первого приводить к числам нет смысла, то для второго это делать, увы, обязательно. Иначе 9, пришедшее из формы, окажется больше 11 из базы. Издержки слабой типизации.
А сравнение на равенство просто попало под раздачу.
Для этого надо просто строки приводить к инту, а тут получаем, что строки с числами не можем сравнить на больше-меньше в алфавитном порядке.
Не совсем уловил смысл реплики, если честно. можно поразвернутее?
Когда мне надо сравнить строки, содержащие числа, как числа, то я их явно приведу к числу. А вот если мне надо отсортировать строки в порядке 1, 11, 2, 222 и т.д., то уменя будут проблемы.
> Иначе 9, пришедшее из формы, окажется больше 11 из базы.
Вы понимаете разницу между "9" > 11 и "9" > "11"? Первый случай я еще могу понять — перед сравнением нужно привести к одному типу. Второй нет.
Это очень просто на самом деле.
Я же пишу: 9 пришло из формы, 11 — из базы. Кто из них не строка? ;-)

Оба строки и оба надо сравнивать как строки. Хотим сравнить как числа, нужно привести их к числам. Хотя бы одно.
По-видимому, ориентир был на очень начинающих пользователей, плохо понимающих такое понятие как тип данных.
нужно привести их к числам.

ну так пхп это и делает! :-)
В строго типизованном языке — обязательно.
В нестрого типизованном языке другие правила.

Вообще, вы все мне так предъявляете, как будто я Расмус, и сам 15 лет назад писал compare_function().
Если вам не нравятся мои объяснения, почему так было сделано — обращайтесь напрямую к нему. Но перед тем попробуйте смоделировать ситуацию, когда числовые строки сравниваются как строки, а не как числа. Не на уровне «потрендеть в комментах», а на реальном проекте. Чтобы оценить приложимость своих идеалов и выполнимость рекомендаций на мало-мальски адекватном объеме задач, а не на одном умозрительном примере. Вдруг несколько частных случаев некорректного сравнения числовых строк окажутся меньшим из зол?
> смоделировать ситуацию, когда числовые строки

Что это за понятие такое, числовая строка? Где определение? Как отличить числовую строку от не числовой? Как привести числовую строку к обычной?
К чему эти вопросы? Должен ли я на них отвечать?
Угу, определения точного нет. Вроде как это строка, для которой is_numeric() возвращает true, но не факт. А самое интересное, что нет способа указать «эту строку всегда воспринимать как строку». И если для проверки на равенство ещё можно использовать строгое сравнение (приходишь к тому, что всегда его используешь, кроме как ситуаций когда нужно неявное приведение, но причину постепенно забываешь), то именно для сравнения (больше-меньше) приходится использовать костыли типа strcmp, которые тоже со своими закидонами.
В нестрого типизованном языке другие правила.

PHP не единственный такой язык в моем арсенале, но единственный с такими проблемами.
Но перед тем попробуйте смоделировать ситуацию, когда числовые строки сравниваются как строки, а не как числа.

Да постоянно, по-моему. Скажем, сравнение паролей или их хэшей. Такое неявное приведение может привести к коллизиям.
Если из базы пришла строка, то это точно строка. Не нужно хранить в базе числа как строки.
Страшное удаление от реалий РНР в этих словах чувствую я:)

API датабазное, увы, несовершенно даже в наши дни, и чтобы получить число, надо сильно постараться.
А уж во времена безраздельного господства libmysql, так и вовсе вариантов не было.
Многие драйвера/обертки выдают все данные из базы как строки. Не говоря о базах, где вообще всё строки.
Похоже, так и есть, давно не писал на пхп, забыл такие нюансы.
Не просто содержащих, а на которых срабатывает is_numeric() — емнип, один анализатор используется.
Для проверки «на число» используется функция is_numeric_string_ex, которая и используется в функции is_numeric. Из этого кода понятно, что в начале строки могут быть не только пробелы, но и некоторые другие пробельные символы.

var_dump(is_numeric("\n\t\r\v\f 0000123")); // bool(true)

Для предотвращения потери точности при сравнении строк с большими числами было добавлено небольшое условие

((oflow1 == 1 && dval1 > 9007199254740991. /*0x1FFFFFFFFFFFFF*/)
|| (oflow1 == -1 && dval1 < -9007199254740991.))


9007199254740991 — это самое большое целое число, которое может быть сохранено в double без потери точности.

$id1 = '9223372036854775888';
$id2 = '9223372036854775899';
var_dump(is_numeric($id1)); // bool(true)
var_dump(is_numeric($id2)); // bool(true)
var_dump($id1 == $id2);     // bool(false)


P.S. да, эта тема уже упоминалась на хабре ( habrahabr.ru/post/142140/, habrahabr.ru/qa/17216/)
Из этого кода понятно, что в начале строки могут быть не только пробелы, но и некоторые другие пробельные символы.

Примечательно, что в доках к is_numeric об этом ни слова. Не говоря о доках к сравнению.
Sign up to leave a comment.

Articles