Pull to refresh

Comments 74

UFO just landed and posted this here
В принципе эти задачи все из жизни. Про именование переменных невидимым символом я узнал случайно, еще в PHP 4, в котором была небольшая бага в функции file_get_contents(); Я очень тогда удивился и не сразу понял что произошло. День отладки ушел.
Ага, мы как-то потратили день на попытки понять, почему explode(' ', $str); не разбивает строку из файла. Оказалось, что пробел там был с каким-то хитрым кодом символа.
UFO just landed and posted this here
Обфускатор? =)
Кажется я начал понимать, что было не так с in_array :) Спасибо, было интересно.
Да, нетипично. Хоть там и есть флаг, указывающий на возможность сравнивать строго, в реалиях я редко видел чтобы он где-то использовался.

bool in_array ( mixed $needle , array $haystack [, bool $strict = FALSE ] )
Плагин к сторму «PHP Inspections (EA Extended)» настаивает на использовании этого флага.
А то, как и использовать === вместо ==.
Тоже сделан в Хорватии? =)
>Но если продолжите итерировать, то удивитесь логике.
Логика действительно странная…

$s = 'Iteration 00';

for ($i = 0; $i < 100; $i++) {
echo $s++, "\n";
}
Ещё интереснее, например, такой комбинированный вариант:

$s = 'Iteration 0x12';

for ($i = 0; $i < 300; $i++) {
     echo $s++, "\n";
}

// Iteration 0x12, Iteration 0x13, ..., Iteration 0x99, Iteration 0y00, Iteration 0y01, ..., 0z99 Iteration 1a00 Iteration 1a01
Я вот каждый раз смотрю на такие статьи и удивляюсь — неужели из тысяч хабраюзеров, пишущих на PHP, никто никогда не читал спеки языка? Там же всё это описано.
Людям свойственно не читать спеки по каждой функции, которую они пытаются применить. Людям свойственно копировать чужой код, особенно если он как-то работает, и потом применять конструкции из этого кода не сильно задумываясь о том, действительно ли они корректные или они дают корректный результат только при определённом наборе входных условий. Даже не задумываясь о том нужны ли они вообще. Порой из-за этого вместо использования встроенных функций используют кривой велосипед. Никогда не встречали ещё альтернативную реализацию explode на самом же PHP? Я с таким сталкивался и в куда более простых языках. А своё упоротое поведение оператора == есть и в JavaScript, например, и о === знают не все и там.

И, самое главное, иногда ожидаешь от языка просто определённого уровня здравого смысла. Да, спеки предупреждают, что его там нет, но их для этого нужно прочитать. Просто представь себе, что ты изучил некоторый набор функций на примерах. Они работают корректно в этих примерах и ты ожидаешь от них корректного поведения и в твоём случае. Ты обязательно полезешь читать по ним документацию? А если ты не профи в PHP и ещё не изучил на своём горьком опыте, что в нём всегда нужно лезть в документацию если ты в неё ещё ни разу не лез по данному вопросу? То-то и оно.
Хорошо, ладно, в своем тезисе «не читал спеки — сам дурак» я перегнул палку, знать всё и всегда действительно невозможно. Но вот аргумент про здравый смысл — он очень слабый, и слабый даже не потому, что в огромном количестве ситуаций здравый смысл для меня и для тебя будет совершенно противоположным, а в первую очередь потому, что здравый смысл зависит от уровня компетентности. Возьми пример из поста с поиском строки в массиве — у некоторого Васи, краем уха слышавшего что-то про РНР, в голове живет некоторый васин_здравый_смысл, который говорит, что если мы сравниваем что-то со строкой «7.1», то все, что не похоже на строку «7.1», должно выдавать false. Но есть еще и разработчики РНР, у который в голове живет разработчицкий_здравый_смысл, и вот этот самый разрабовский смысл осведомлен о том, что такое динамическая типизация, как с ней жить и к чему она приводит. Кто из них правее? Кждый по-своему, безусловно, но заметь, что при этом типичный Вася из комментариев не говорит «ох, какой тупой я, не знал, что РНР работает именно так», он говорит «ох, какой тупой РНР, он работает совсем не так, как я ожидал». В моей личной системе координат (которая, повторюсь, моя личная, я никому не собираюсь ее навязывать) это очень похоже на поведение человека, который попал себе молотком по пальцу и винит в этом молоток.

Второе — про «ты изучил некоторый набор функций на примерах». Я не думаю, что в общем случае это хороший вариант. Это традиционная дилемма с выбором между безопасностью и комфортом — ты можешь либо изучать функции по примерам, думая, что понял, как это работает, но при этом знать, что что-то где-то может пойти не так и «безопасность» твоих действий не стопроцентная, либо читать подробное описание каждой функции в доке (параноидальный вариант — еще и перепроверять в исходниках) и знать, что все делаешь правильно, но комфортом тут и не пахнет. Точно так же часть людей держит всю свою информацию в уберзащищенных криптоконтейнерах, зашифрованных 8192-битным ключем, который они держат только в голове, а другая часть людей использует везде один и тот же логин с паролем 123456. Первым безопаснее, вторым проще, но если у вторых что-то все-таки случается, думаю, им стоит винить в этом себя, а не какие-то загадочные внешние силы.
На счёт «здравого смысла» ты прав. Просто нужно помнить о том, что в PHP типизация «агрессивная».

Например, в JS (тоже ведь динамическая типизация):
'0e830400451993494058024219903391' == '0e462097431906509019562988736854' → false
'0e830400451993494058024219903391' == 0e462097431906509019562988736854 → true
Во втором случае происходит сравнение с числом и JS пытается привести строку к числу. В результате получаем ту же проблему, что и в PHP, но при сравнении двух строк происходит именно сравнение двух строк и не более того. Т.е. в нём типизация «ленивая» — если нет повода приводить строку к числу JS этого и не будет делать. PHP же обязательно попробует привести все строки к числам если специально не указать ему этого не делать. Отсюда и проистекает якобы не очевидное поведение in_array. На самом деле оно очевидное. Наоборот, проблемой станет ситуация, когда понадобится выполнить точную проверку на вхождение — вот тогда и придётся лезть в спеки.

Хотя лично с моей точки зрения такая агрессивная типизация как-раз и противоречит здравому смыслу. Точнее даже не она, а принцип «вернуть хоть какой-то положительный результат». Зачем делать то, о чём тебя даже неявно не просили? Например, PHP старается выполнить код даже если в нём есть явно невалидные куски. Да вот хоть тот же пример со ссылкой перед echo. С какой стати это вообще должно работать? А ведь работает. Подавляющее большинство ЯП в случае ошибки в коде предпочтут выдать сообщение об ошибке и остановить выполнение — дальнейшее выполнение ведь приведёт лишь к непредсказуемым последствиями, но только не PHP. Разве это можно назвать разумным поведением?

Ну а на счёт изучения на примерах — подавляющее большинство делает именно так и тут не спасают даже использование в качестве примеров таковых из учебников. Ну вот, например, такая вот статья в одном из онлайн-учебников по PHP (слева в меню: Учебник PHP — «Для Чайника»). В ней хоть слово есть о том, что у in_array может быть третий параметр и что он вообще значит? Хорошо он хоть далеко не первый в выдаче того же Гугла.
Да вот хоть тот же пример со ссылкой перед echo. С какой стати это вообще должно работать? А ведь работает.
Оно также работает и в C, C++ и C#. При некоторых условиях заработает в Java. так что ничего удивительного в том, что оно работает еще и в PHP — нет.
Хе, да, конкретно это — действительно валидный код. К списку ещё можно JS добавить и наверняка ещё некоторые языки по тем же причинам.
Собственно то, к чему к меня реальная претензия, это не совсем вина самого PHP. Просто есть в нём специально оператор @ для игнорирования ошибок и мне приходилось отлаживать чужой код… который им активно пользовался. Мягко говоря неприятное занятие. Зачем его вообще ввели если есть try-catch?
Его ввели задолго до try-catch, еще в то время, когда единственным способом «обработки» ошибок, который знал PHP, был вывод некрасивого сообщения в рамочке на страницу.

Поскольку тогда в моде был режим register_globals = on, а функцию isset еще не придумали, программист далеко не всегда мог знать заранее, какие переменные у него в программе вообще существуют.

Вот потому-то у добавили тот оператор, чтобы можно было проверить существование переменной и не словить предупреждение при этом.
Это объясняет, но не извиняет само его существование. Можно же было сразу ввести try-catch. Заодно можно было сразу ввести finally для него же, а не говорить вплоть до версии 5.5, что эта конструкция «не имеет смысла в php» и отказываться её реализовывать.
Можно же было сразу ввести try-catch

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

а не говорить вплоть до версии 5.5

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


есть такая директивка, как track_errors, с которой можно сделать вот так:

<?php
@strpos();
echo $php_errormsg; // Wrong parameter count for strpos()


Другое дело что мало кто об этом знает (я вот не знал, пока не пришлось дебажить код напичканный всяким трэшем), но жить с этим делом кое как можно.
Надеюсь мне больше никогда не придётся иметь дело с таким кодом, хоть такие надежды обычно тщетны.
А пример с инкрементацией букв из статьи это уже вообще за гранью добра и зла, а не противоречит здравому смыслу.
Справедливости ради, спека по языку официальная появилась не так давно.
> PHP видит 0 и думает что это число.
Здесь вы немного неправы. PHP видит 0e[0-9]+ и думает что это float число, которое приводится к 0. Подробнее вот здесь blog.whitehatsec.com/magic-hashes
Хорошее замечание. Внесу поправку
А что вы думаете о таком коде?

$s = 'Iteration а';

for ($i = 0; $i < 100; $i++) {
echo $s++, "\n";
}
Кстати. когда писал про странную логику имелось в виду другое. Сейчас когда повторил на PHP 5.6.9, то уже нет того. что имел в виду, а именно: там продолжение было в виде шестнадцатиричных чисел. Сейчас все ок, просто инкрементируется число.
И да, с символом интересная тема, добавлю в статью, с вашего позволения. Я как-то забыл про это.
Тогда уж можно добавить в примеры разницу в инкременте строк «Iteration 0» и «Iteration0» (если их раз по 100 инкрементировать).
Да, там уже не очевидная логика начинается. Спасибо за комментарий.
За «коллизии» при проверке md5 — отдельное спасибо, братюнь. Столкнулся недавно с таким нежданчиком.
Супер! Рад что помог =)
А как вам такое?

Следующий код не выдаст никаких ошибок:

<?php
class A {
    const C = B::C;
}

class B {
    const C = A::C;
}


Для PHP всё равно, что константы ссылаются друг на друга. А что будет, если попробовать распечатать одну из них?

var_dump(A::C);


Этот код уже печатает

Fatal error: Cannot declare self-referencing constant 'B::C' in /Users/nasretdinov/fails.php on line 10
Супер! Кладу себе в копилку. Не знал, не сталкивался.
Я, кстати, не объяснил, почему так происходит:

Когда мы пишем «class A { const C = SOMETHING; }», PHP просто «создает алиас» для SOMETHING с именем «A::C». При этом, он не проверяет, что значение ссылается на существующие константы.

Приведу пример:

<?php
class A {
    const C = SOMETHING;
}

var_dump(A::C);


Этот код выведет следующее:

Notice: Use of undefined constant SOMETHING - assumed 'SOMETHING' in /Users/nasretdinov/something.php on line 6
string(9) "SOMETHING"


Весьма неожиданно, неправда ли? Ведь мы обращались к константе класса «A::C», а ошибку получили про то, что константа SOMETHING не определена.

Если бы мы вообще не обращались к этой константе, то никаких ошибок бы не было. Не очень понятно, как назвать то, что используется в PHP — это нечто покруче, чем «позднее статическое связывание».

Если определить константу до первого обращения к «A::C», то всё будет работать, как ожидается:

<?php
class A {
    const C = SOMETHING;
}

define('SOMETHING', "Value of something constant");
var_dump(A::C); // выведет string(27) "Value of something constant"
Ведь мы обращались к константе класса «A::C», а ошибку получили про то, что константа SOMETHING не определена.

Простите, а какую ошибку вы рассчитывали получить? Как по мне все довольно таки ожидаемо и логично.

По поводу первого примера — константы рассчитываются не при компиляции а при первом обращении. Язык динамический, все такое. Все в рантайме. Мне кажется это более чем логично. А вот из контанты одного класса ссылаться на другую — вот это диковинка для меня.
Последний пример особенно позабавил, не сталкивался с этим в PHP. Оказалось, что доки про это предупреждают:
Warning Reference of a $value and the last array element remain even after the foreach loop. It is recommended to destroy it by unset().

– но все равно такая реализация пугает. Почему не $foo является ссылкой на значение элемента массива, а наоборот, элемент массива является ссылкой на $foo – это какой-то взрыв мозга. Вот то ли дело в чистом Си, или хотя бы в Perl .)
Ух, Саша, не ожидал, что твой пост меня так увлечет :-) Провел больше часа в чтении документации и исходников, нашел интересный пост (точнее, там их серия), обнаружил, что в PHP (начиная с 5.3) появился нормальный сборщик мусора.

Краткий итог таков: лучшее описание механизма references в php приведено в документации:
The closest analogy is with Unix filenames and files — variable names are directory entries, while variable content is the file itself. References can be likened to hardlinking in Unix filesystem.

А лучший комментарий на эту тему – там же рядом:
What References are not: References.

References are opaque things that are like pointers, except A) smarter and B) referring to HLL objects, rather than memory addresses. PHP doesn't have references. PHP has a syntax for creating *aliases* which are multiple names for the same object. PHP has a few pieces of syntax for calling and returning «by reference», which really just means inhibiting copying. At no point in this «references» section of the manual are there any references.

В общем, теперь я вроде неплохо понял, как это работает, но принципиальное отличие в подходе PHP от многих других языков в очередной раз удивило.
Афигеть! Классное дополнение. Спасибо за коммент с интересными ссылками.
Вот еще забавный кусок кода (специально без php-подсветки, с ней легко догадаться).

<?php
http://google.com
echo 3 + 5;

Что выведет, если вообще выведет?
Вау! =) А это прям вопрос на олимпиаду. Неожиданно.
Взял в коллекцию В этом сентябре снова будем делать хак квест ко дню разработчика. Так что новые вопросы с заковыркой мне не помешают.
Спасибо! От меня +1 в карму.
Тоже из жизни. Случайно вставил ссылку из буфера в код. Да и не заметил этого.
спойлер
А оно работает. Через пару недель снова полез в тот участок кода и несколько удивился увиденному.
И вам тоже спасибо за подборку. В избранное однозначно.
Слушайте, я как-то погорячился с неожиданностью. Сначала увидел строку и не придал значения. А сейчас писал код и как-то машинально вспомнил ваш пример. Рассказываю почему оно работает:

# http: - лейбл. метка на которую можно зайти через goto
// - комментарий

http: // все что угодно

goto http;



Ну вот зачем вы так взяли и всем рассказали?.. :)
Хм, а ведь оно и на C++ «работать» будет (я про вторую строчку, не про третью)
А что должно получиться? У меня выводит

8

Правда у меня php старый.
По поводу штучки с неймспейсами — насколько помню эта багофича используется в php-библиотеке Go AOP
Вы кое-что забыли. В примере со ссылками:

$cnt = getCount( $foo['bar'] );
var_dump($foo); //внезапно ['bar' => null]
Да, хорошее дополнение. Спасибо!
переменная «пробел» — пожалуйста, пустая строка — тоже:
${""} = 444;
${" "} = 555;
var_dump(${""},${" "});


Поведение понятно, если учесть, что все переменные — это всегда части массива $GLOBALS;
навеяло)

$x = 'key';
$key = 'value';
echo ${$x};
Можно даже без {}
<?php
$x = 'key';
$key = 'value';
$value = 'we need to go deeper';
echo $$$x; 
Забавно)) незнал, спасибо.
$x = 0;
$y = '  ';
//$y = '.';
//$y = '-';
//$y = '0x0';
//$y = 'sdfhsdkhfkjsdhfdksj';
$x == $y; //true


Всё же более наглядно. А то некоторые могут интерпретировать, что $y='x' это отсылка к переменной $x, а не к приведению типа «справа».
Мне РНР напоминает IE — тоже огромная куча милых нелогичных багофич, которые надо просто понять и простить. Причем многие из них мигрируют из версии в версию годами, а иногда фиксятся, и тогда надо помнить не только багофичу и объяснение, почему [было] так, но и версию языка, когда это было.

Я когда с РНР работал, через какое-то время подустал отслеживать миграцию багофич и помнил только основные принципы. Сейчас пишу на Питоне, тут тоже не полная гладь и тишь, но отсутствиые необходимости слежки за зоопарком здорово даёт расслабиться и заниматься непосредственно программированием.
Немного изменил пример с инкрементацией, получилось странно:

		$a = 1;
		var_dump ($a + ($a = 2));

		int 4 - все верно


		$a = 1;
		var_dump ($a + $a + ($a = 2));

		ErrorException [ Fatal Error ]: Unsupported operand types


Но:
		$a = 1;
		if (($a + $a + ($a = 2)) == 2)
			echo '==';

		==


Почему так? PHP 5.5.9
все дело в оптимизации (может быть), но как минимум все дело в том, как хранятся переменные.
Дело совсем не в оптимизации, а в приоритетах операций и последовательности их обработки.
Спасибо за комментарий! Занесу исправления в текст.
И все равно более логичным поведение не стало. Обычно приоритеты операций влияют лишь на процесс синтаксического разбора кода — но не на порядок выполнения.
Выполняем синтаксический разбор в соответствии с приоритетами операций:
   +
  / \
 2   *
    / \
   2   2


Поскольку операция + зависит от результата операции *, выполнить их в неверном порядке уже невозможно.
Sign up to leave a comment.

Articles