Сборник PHP ненормальностей или что надо знать чтобы не сойти с ума и не прострелить себе что-нибудь


Прочитал статью mnv: "Приведение типов в PHP == табурет о двух ножках?" и захотелось в комментариях добавить немного дополнений, но… Но потом увидел комментарий и понял, что лучше дополню статью тем, про что мало кто пишет и мало где это имеется в централизованном виде. Вроде бы всем известная тема, а все же кому-то в новинку. Это не совсем про приведение типов, но они тоже есть. Это про особенности, зная которые можно делать меньше ошибок. Если интересно, го под кат, я создал!

PHP не плохой и не хороший. Он для своих задач и с ними он справляется. При этом этот язык программирования имеет много разных багофич. Можно на них жаловаться и негодовать. А можно просто про них знать и уметь их обходить или применять. Не не не, я не призываю так писать, я просто говорю, что если захочется, то…

Прежде чем судить, давайте договоримся!


Давайте рассматривать этот пост — как развлекательный. Т.е. это задачки не для собеседований и не для продакшена. Это просто примеры задач на олимпиаду, где можно получить звание "Я — интепретатор PHP!".

Думай как PHP… Чувствуй как PHP… Будь PHP!


Задачки взяты из нашего квеста, который мы делали на "День девелопера", отмечаемый в нашей компании. Наша коллега даже писала подробную статью про то, как в Tutu.ru чествуют труд айтишников в статье "Как отметить день программиста на работе и сделать всех довольными?". Так что повторюсь, это не для продакшена и не для собеседования. Это ради фана!

Про числа


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

Каков результат конкатенирования следующих строк? (да, так не пишут, но у нас же олимпиада):

<?php
{
    print "a" . 2;
    print "a".2;
}

Что будет?
В последней строке PHP попытается привести запись к числу с плавающей запятой. Но это приведет к ошибке.
PHP Parse error: syntax error, unexpected '.2' (T_DNUMBER) in Command line code on line 1

Поиск строки в массиве

Это вполне себе боевая ситуация. Такое может встретиться в коде некоторых CMS или реальных проектов.

<?php
 
$a = ['7.1'];
 
in_array('7.1', $a);
in_array('7.1abc', $a);
in_array('7.10', $a);
in_array('7.100000000000000009123', $a);

Вспоминаем про фишку PHP и получаем правильный ответ:
in_array('7.1', $a); // true
in_array('7.1abc', $a); // false
in_array('7.10', $a); // true
in_array('7.100000000000000009123', $a); // true

Что бы избежать ошибок, используйте 3й аргумент: указание типа сравнения.

bool in_array ( mixed $needle, array $haystack [, bool $strict = FALSE ] )

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


Как в PHP переопределить TRUE?


Вопрос на засыпку и ради академического интереса. Если очень хочется, то:
<?php
 
// Так сделать не получится в глобальной области
// PHP Notice:  Constant true already defined in ...
namespace {
   define('true', false);
}
 
// Но вот в неймспейсе - пожалуйста
namespace Hack {
    define('Hack\\true', false);
    var_dump(true === false); // true
}

Если говорить про PHP7, то там работать такой код не будет и переопределить эти константы будет невозможно.

Валидный ли скрипт?


<?php

$   = 1;
$   = 2;
$   = $  + $ ;

var_dump(   $   );

//EOF//

Да
Символ с десятичным индексом 160 входит в таблицу разрешенных символов для именования переменных в PHP. В Windows его набрать можно как Alt+0160.

Выполнить любой ценой


Это просто задачка на подумать. Опять же про приведение типов. Просто головоломка.

<?php

    if ( $x == false && $y == true && $x == $y )
    {
        echo "Yuo crazy developer!";
    }

Один из вариантов
$x = 0;
$y = 'x';

Безумная логика


А вот еще классный пример особенностей интепретатора. Давайте ответим на вопрос, что будет?

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

А если усложним?

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

Если ответили на первые 2 вопроса, то тогда вам не составит труда интерпретировать вот эту задачку:

<?php

$a = 1;
$b = 1;

var_dump( ($a + $a + $a++) === ($b + $b++) );


Ответ в следующем абзаце.

Что-то пошло не так, да?
И в 1-м, и во 2-м случае ответ 3, а значит, 3-й пример выдаст true.

Как работает первый пример?
Операция сложения левоассоциативна — разбор агрументов начинается слева направо. Двигаясь таким образом, парсер видит выражение в котором две операции — сложение и постинкремент, у постинкремента приоритет выше, поэтому вычисляется сначала он — возвращая в качестве значения «1» и увеличивая переменную на единицу. Потом вычисляется операция сложения, складывая полученную единицу с двойкой (так как постинкремент увеличил значение переменной на единицу). Получается «три».

Похожим образом обрабатывается умножение вместе со сложением: 2 + 2 * 2 = 6, а не 8, потому что умножение имеет более высокий приоритет.

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

Больше деталей и картинка
В байт-кодах всё перечисленное выглядит следующим образом:

Тут с восьмой строки начинается второй пример (правда присваивание единицы во втором примере я опустил — как видите второй операции ASSIGN нет).

Источник


Но на деле не логично. Так что не ломайте голову, все работает правильно. Но не так, как вы ожидали. В других языках, к примеру, Javascript, ответ будет другой. Проверьте. 2й вариант ведет себя одинаково с тем же JS. Но вот 1й вызывет вопросы, если смотреть на результаты в разных языках программирования.

Слабо проитерировать строку?


Есть задача проитерировать строку:

<?php
 
$s = 'Iteration'; // <- можно модифицировать
 
for ($i = 0; $i < 10; $i++) {
    // echo ???, "\n";
}
 
//EOF//

На выходе нужен следующий результат:

Iteration 0
Iteration 1
Iteration 2
Iteration 3
Iteration 4
Iteration 5
Iteration 6
Iteration 7
Iteration 8
Iteration 9


Ваши варианты?
А вот вам мой вариант
<?php
 
$s = 'Iteration 0';
 
for ($i = 0; $i < 100; $i++) {
    echo $s++, "\n";
}
 
//EOF//

Если в конце стоит 1 цифра, то итерация будет повторяться от 0 до 9 при цикле. Если хотите получить от 0 до 99, то нужно уже поставить 2 числа: «Iteration 00». А что если хочется вывести алфавит? Тогда ставим в конце букву a, если хотим вывести все по порядку:

Iteration a
Iteration b
Iteration c
Iteration d
Iteration e
Iteration f
Iteration g
Iteration h
Iteration i
Iteration j
Iteration k
Iteration l
Iteration m
Iteration n
...

В PHP7 ничего не меняется. такое поведение сохраняется. С учетом того, что это поведение существует давно, где-нибудь на хакатонах можно применять такой подход. Ну или опять же, джаст 4 фан. Поиграйте с этим, там есть еще сюрпризы, не хочу расписывать. Но если продолжите итерировать, то удивитесь логике.

Значения по ссылке


Ссылки в PHP, штука вещь хорошая! Но есть нюансы. У нас нигде не определен массив $foo. Это должно вызывать ошибку…

<?php

function getCount(&$a)
{
	return count($a);
}

$cnt = getCount( $foo );
var_dump($cnt);


// и если сделать так

$cnt = getCount( $foo['bar'] );
var_dump($cnt);


Но все будет ок. Ошибка будет только в случае, если убрать амперсанд, тогда получим:
PHP Notice: Undefined variable: foo in /www/sites/majorov.su/***/a.php on line 8
int(0)
PHP Notice: Undefined variable: foo in /www/sites/majorov.su/***/a.php on line 14
int(0)

Несуществующий валидный код


Давайте пофантазируем. У вас мощный проект с 10 летней историей. И у вас появилась задача рефакторинга и реинжиниринга старого кода.

И вот вы правите код и сходите с ума. Почему? Ну вот потому, что, допустим, вы не понимаете почему у вас работает то, что не должно.

<?php
 
class Bar {
    public $foo = 1;
}
 
$Obj1 = new Bar( Foo.bar() ); // Foo нигде не определен! Он должен вызвать фатал
$Obj2 = new stdClass( getFoo( $Obj1 ) ); // getFoo() не существует, почему он работает?

Если нет конструктора, то код, переданный в конструктор не вызывает ошибок. Он парсится, но не выполняется, так как оптимизатор игнорирует эти строки. Это экономит нам время на обработку. Ведь чтобы показать ошибку, нужно знать какая ошибка (код, тип, номер строки...). А тут просто идет пропуск части кода, так как нет в нем смысла. Это классно, правда. Но вы должны знать про это, иначе вам взорвет это мозг.

ACL, MD5 и… Коллизия?


Представим что у вас есть CMS. И вот там есть что-то вроде такого (поверьте, много где такого кода (не именно такого, но работающего по такому принципу, можно найти в рунете).

<?php

// Допустим брутфорсим пользователя admin

// Получили пользовательский пароль QNKCDZO
$_POST = ['pass' => 'QNKCDZO'];
$userPass = md5($_POST['pass']);

// Есть пароль в базе такого вида 240610708
$actualPassInDb = md5('240610708');

$autorizied = false;

// сделали проверку
if ( $userPass == $actualPassInDb ) {
	// Авторизировали пользователя
	$autorizied = true;
}
else {
	/*header*/var_dump("location: /error/");
	die;
}

А что не так? Вроде все ок. Пароли же разные. Разве нет?
Давайте взглянем на md5() хеши паролей:
  • QNKCDZO, в MD5 = string(32) «0e830400451993494058024219903391»
  • 240610708 = string(32) «0e462097431906509019562988736854»

Поняли? PHP видит 0 и думает что это число. Точнее PHP видит 0e[0-9]+ и думает что это float число, которое приводится к 0. Подробнее описано по ссылке: https://blog.whitehatsec.com/magic-hashes/

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

Foreach и ссылки на ключи


На последок напомню, что, с ссылками надо быть аккуратнее.
Вот пример, как получить не то, что ожидали:

<?php

$array = ['foo', 'bar'];

foreach ($array as $k => &$foo){
	$foo .= $k;
}

var_dump($array);

foreach ($array as $foo) {
	var_dump($foo);
}


Что ожидаем? А что получаем?

А получаем вот что:

array(2) {
  [0]=>
  string(4) "foo0"
  [1]=>
  &string(4) "bar1" // важно, здесь указатель !
}
string(4) "foo0"
string(4) "foo0"

Из-за спрятавшегося указателя при следующей итерации мы получаем доступ к другому значению.

Собственно это весь мой комментарий к той статье, который я хотел добавить. Учите особенности языка. Учите их не ради того, чтобы валить на собеседованиях. Учите их, чтобы самому в такую яму не попасть. Не брезгуйте. Вы так не пишите, так другие так могут написать. А кто будет объяснять им почему не так и что не так? А как объяснить то, чего не знаешь?

Мир вам, девелоперы. Я писал на PHP 12 лет, и сейчас уже 3 года как во фронтенд разработке, схожу с ума с JavaScript, но это уже совсем другая история. Но иногда хочется потрогать это самый PHP.

Так же советую к прочтению статью от AlexLeonov «Готовимся к собеседованию по PHP: ключевое слово «static»», там есть интересные моменты, которые я не стал описывать.

P.S.: На картинке PHP MV 9. Пистолет PHP (Prvi Hrvatski Pistolj — первый хорватский пистолет) был в спешном порядке разработан в отделившейся от союзной Югославии Хорватии в начале девяностых годов 20 века, когда страна отчаянно нуждалась в оружии из-за возникшей на руинах СФРЮ войны. Пистолет, вобравший в себя черты таких известных и достаточно удачных образцов как Beretta 92 и Walther P38, вышел гораздо менее удачным и имел проблемы с надежностью. Выпуск его был довольно непродолжительным и позже он был заменен на вооружении Хорватской армии гораздо более удачным пистолетом HS 2000.

Пистолет PHP использует автоматику с коротким ходом ствола, запирание осуществляется при помощи расположенной ниже ствола качающейся личинки. Возвратная пружина расположена под стволом. Ударно-спусковой механизм курковый, двойного действия (самовзводный). Слева на рукоятке расположен рычаг безопасного спуска курка с боевого взвода. Магазин двухрядный, емкостью 15 патронов.

Будьте аккуратны, не прострелите себе чего-нибудь!

UPD: в комментариях юзер smart дал хорошие линки на документацию, для тех, кто хочет разобраться в деталях

Комментарии (74)


  1. vlreshet
    09.06.2015 10:39
    +34

    Зашёл с мыслями «да чего я там могу не знать, не такой страшный чёрт странный PHP как его описывают». Дочитывал с мыслями «госсподи, да я же ничерта не знаю». Отличная статья, спасибо.


    1. 0xy Автор
      09.06.2015 10:51

      Спасибо!


  1. LionAlex
    09.06.2015 11:11

    Последнее как-то нашли в старом легаси коде огромного проекта.


    1. 0xy Автор
      09.06.2015 11:16

      В принципе эти задачи все из жизни. Про именование переменных невидимым символом я узнал случайно, еще в PHP 4, в котором была небольшая бага в функции file_get_contents(); Я очень тогда удивился и не сразу понял что произошло. День отладки ушел.


      1. LionAlex
        09.06.2015 11:20
        +1

        Ага, мы как-то потратили день на попытки понять, почему explode(' ', $str); не разбивает строку из файла. Оказалось, что пробел там был с каким-то хитрым кодом символа.


  1. Alexeyco
    09.06.2015 11:16

    > $ = $ + $;

    О, да, я поначалу, будучи новичком тоже на этом попался (сарказм). Вот зачем нужны все эти подвыверты, я понять не могу. Эти тройные тулупы с выходом от программирования…


    1. 0xy Автор
      09.06.2015 11:16
      +3

      Обфускатор? =)


  1. kentastik
    09.06.2015 11:42
    +1

    Кажется я начал понимать, что было не так с in_array :) Спасибо, было интересно.


    1. 0xy Автор
      09.06.2015 11:50
      +3

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

      bool in_array ( mixed $needle , array $haystack [, bool $strict = FALSE ] )
      


      1. gaelpa
        09.06.2015 18:35
        +1

        Плагин к сторму «PHP Inspections (EA Extended)» настаивает на использовании этого флага.


        1. kalessil
          17.06.2015 09:19

          А то, как и использовать === вместо ==.


  1. BubaVV
    09.06.2015 11:42
    +25

    image


    1. 0xy Автор
      09.06.2015 11:46

      Тоже сделан в Хорватии? =)


  1. GEOgraf
    09.06.2015 11:54
    +2

    >Но если продолжите итерировать, то удивитесь логике.
    Логика действительно странная…

    $s = 'Iteration 00';

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


    1. BearOff
      17.06.2015 09:19
      +1

      Ещё интереснее, например, такой комбинированный вариант:

      $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
      


  1. JustRoo
    09.06.2015 12:07
    -11

    Я вот каждый раз смотрю на такие статьи и удивляюсь — неужели из тысяч хабраюзеров, пишущих на PHP, никто никогда не читал спеки языка? Там же всё это описано.


    1. Lain_13
      16.06.2015 13:06

      Людям свойственно не читать спеки по каждой функции, которую они пытаются применить. Людям свойственно копировать чужой код, особенно если он как-то работает, и потом применять конструкции из этого кода не сильно задумываясь о том, действительно ли они корректные или они дают корректный результат только при определённом наборе входных условий. Даже не задумываясь о том нужны ли они вообще. Порой из-за этого вместо использования встроенных функций используют кривой велосипед. Никогда не встречали ещё альтернативную реализацию explode на самом же PHP? Я с таким сталкивался и в куда более простых языках. А своё упоротое поведение оператора == есть и в JavaScript, например, и о === знают не все и там.

      И, самое главное, иногда ожидаешь от языка просто определённого уровня здравого смысла. Да, спеки предупреждают, что его там нет, но их для этого нужно прочитать. Просто представь себе, что ты изучил некоторый набор функций на примерах. Они работают корректно в этих примерах и ты ожидаешь от них корректного поведения и в твоём случае. Ты обязательно полезешь читать по ним документацию? А если ты не профи в PHP и ещё не изучил на своём горьком опыте, что в нём всегда нужно лезть в документацию если ты в неё ещё ни разу не лез по данному вопросу? То-то и оно.


      1. JustRoo
        16.06.2015 18:51
        +2

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

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


        1. Lain_13
          16.06.2015 20:19

          На счёт «здравого смысла» ты прав. Просто нужно помнить о том, что в PHP типизация «агрессивная».

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

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

          Ну а на счёт изучения на примерах — подавляющее большинство делает именно так и тут не спасают даже использование в качестве примеров таковых из учебников. Ну вот, например, такая вот статья в одном из онлайн-учебников по PHP (слева в меню: Учебник PHP — «Для Чайника»). В ней хоть слово есть о том, что у in_array может быть третий параметр и что он вообще значит? Хорошо он хоть далеко не первый в выдаче того же Гугла.


          1. mayorovp
            17.06.2015 05:43

            Да вот хоть тот же пример со ссылкой перед echo. С какой стати это вообще должно работать? А ведь работает.
            Оно также работает и в C, C++ и C#. При некоторых условиях заработает в Java. так что ничего удивительного в том, что оно работает еще и в PHP — нет.


            1. Lain_13
              17.06.2015 11:44

              Хе, да, конкретно это — действительно валидный код. К списку ещё можно JS добавить и наверняка ещё некоторые языки по тем же причинам.
              Собственно то, к чему к меня реальная претензия, это не совсем вина самого PHP. Просто есть в нём специально оператор @ для игнорирования ошибок и мне приходилось отлаживать чужой код… который им активно пользовался. Мягко говоря неприятное занятие. Зачем его вообще ввели если есть try-catch?


              1. mayorovp
                17.06.2015 13:14
                +1

                Его ввели задолго до try-catch, еще в то время, когда единственным способом «обработки» ошибок, который знал PHP, был вывод некрасивого сообщения в рамочке на страницу.

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

                Вот потому-то у добавили тот оператор, чтобы можно было проверить существование переменной и не словить предупреждение при этом.


                1. Lain_13
                  17.06.2015 13:52

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


                  1. Fesor
                    17.06.2015 14:04
                    +1

                    Можно же было сразу ввести try-catch

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

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

                    А вот это хороший вопрос, я честно не помню почему так…


                1. Lain_13
                  17.06.2015 14:01

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


              1. Fesor
                17.06.2015 14:02
                +1

                @ для игнорирования ошибок и мне приходилось отлаживать чужой код… который им активно пользовался.


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

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


                Другое дело что мало кто об этом знает (я вот не знал, пока не пришлось дебажить код напичканный всяким трэшем), но жить с этим делом кое как можно.


                1. Lain_13
                  17.06.2015 15:06
                  +1

                  Надеюсь мне больше никогда не придётся иметь дело с таким кодом, хоть такие надежды обычно тщетны.


        1. Lain_13
          16.06.2015 20:24
          +1

          А пример с инкрементацией букв из статьи это уже вообще за гранью добра и зла, а не противоречит здравому смыслу.


    1. Fesor
      16.06.2015 14:10
      +1

      Справедливости ради, спека по языку официальная появилась не так давно.


  1. egor_nullptr
    09.06.2015 12:21
    +2

    > PHP видит 0 и думает что это число.
    Здесь вы немного неправы. PHP видит 0e[0-9]+ и думает что это float число, которое приводится к 0. Подробнее вот здесь blog.whitehatsec.com/magic-hashes


    1. 0xy Автор
      09.06.2015 12:32
      +2

      Хорошее замечание. Внесу поправку


      1. GEOgraf
        09.06.2015 12:41
        +3

        А что вы думаете о таком коде?

        $s = 'Iteration а';

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


        1. 0xy Автор
          09.06.2015 12:51

          Кстати. когда писал про странную логику имелось в виду другое. Сейчас когда повторил на PHP 5.6.9, то уже нет того. что имел в виду, а именно: там продолжение было в виде шестнадцатиричных чисел. Сейчас все ок, просто инкрементируется число.
          И да, с символом интересная тема, добавлю в статью, с вашего позволения. Я как-то забыл про это.


          1. smart
            09.06.2015 13:57

            Тогда уж можно добавить в примеры разницу в инкременте строк «Iteration 0» и «Iteration0» (если их раз по 100 инкрементировать).


            1. 0xy Автор
              09.06.2015 14:16

              Да, там уже не очевидная логика начинается. Спасибо за комментарий.


  1. vogel
    09.06.2015 12:33
    +8

    За «коллизии» при проверке md5 — отдельное спасибо, братюнь. Столкнулся недавно с таким нежданчиком.


    1. 0xy Автор
      09.06.2015 12:35
      +3

      Супер! Рад что помог =)


  1. youROCK
    09.06.2015 12:58
    +5

    А как вам такое?

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

    <?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
    


    1. 0xy Автор
      09.06.2015 13:00
      +2

      Супер! Кладу себе в копилку. Не знал, не сталкивался.


      1. youROCK
        09.06.2015 20:11
        +2

        Я, кстати, не объяснил, почему так происходит:

        Когда мы пишем «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"
        


        1. 0xy Автор
          09.06.2015 20:44

          Спасибо!


        1. Fesor
          10.06.2015 00:09
          -2

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

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

          По поводу первого примера — константы рассчитываются не при компиляции а при первом обращении. Язык динамический, все такое. Все в рантайме. Мне кажется это более чем логично. А вот из контанты одного класса ссылаться на другую — вот это диковинка для меня.


    1. Tenkoff
      10.06.2015 10:48
      +1

      Оставлю это тут — Evaluation strategy


  1. tgz
    09.06.2015 13:15
    +8

    nophp.ru


  1. smart
    09.06.2015 13:56
    +2

    Последний пример особенно позабавил, не сталкивался с этим в 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 .)


    1. smart
      09.06.2015 17:04
      +4

      Ух, Саша, не ожидал, что твой пост меня так увлечет :-) Провел больше часа в чтении документации и исходников, нашел интересный пост (точнее, там их серия), обнаружил, что в 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 от многих других языков в очередной раз удивило.


      1. 0xy Автор
        09.06.2015 17:06

        Афигеть! Классное дополнение. Спасибо за коммент с интересными ссылками.


  1. maximw
    09.06.2015 15:06
    +3

    Вот еще забавный кусок кода (специально без php-подсветки, с ней легко догадаться).

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

    Что выведет, если вообще выведет?


    1. 0xy Автор
      09.06.2015 15:43

      Вау! =) А это прям вопрос на олимпиаду. Неожиданно.
      Взял в коллекцию В этом сентябре снова будем делать хак квест ко дню разработчика. Так что новые вопросы с заковыркой мне не помешают.
      Спасибо! От меня +1 в карму.


      1. maximw
        09.06.2015 15:46

        Тоже из жизни. Случайно вставил ссылку из буфера в код. Да и не заметил этого.

        спойлер
        А оно работает. Через пару недель снова полез в тот участок кода и несколько удивился увиденному.


      1. maximw
        09.06.2015 16:02

        И вам тоже спасибо за подборку. В избранное однозначно.


        1. 0xy Автор
          23.06.2015 18:55
          +3

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

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


          1. mayorovp
            23.06.2015 19:37
            +3

            Ну вот зачем вы так взяли и всем рассказали?.. :)


    1. mayorovp
      09.06.2015 16:48
      +1

      Хм, а ведь оно и на C++ «работать» будет (я про вторую строчку, не про третью)


    1. nomadmoon
      10.06.2015 04:48

      А что должно получиться? У меня выводит

      8

      Правда у меня php старый.


      1. mayorovp
        10.06.2015 06:02

        Так и должно быть


  1. andrewnester
    09.06.2015 17:26
    +1

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


  1. impwx
    09.06.2015 18:35
    -1


  1. truezemez
    09.06.2015 18:49
    +2

    Вы кое-что забыли. В примере со ссылками:

    $cnt = getCount( $foo['bar'] );
    var_dump($foo); //внезапно ['bar' => null]
    


    1. 0xy Автор
      09.06.2015 20:43

      Да, хорошее дополнение. Спасибо!


  1. REZ1DENT3
    09.06.2015 22:10

    var_dump('0e1212' == '0e11'); // true


  1. lastbronetrain
    09.06.2015 23:38

    Про конструктор порадовало) Спасибо


  1. romy4
    10.06.2015 13:48

    переменная «пробел» — пожалуйста, пустая строка — тоже:

    ${""} = 444;
    ${" "} = 555;
    var_dump(${""},${" "});
    


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


    1. GEOgraf
      10.06.2015 14:54

      навеяло)

      $x = 'key';
      $key = 'value';
      echo ${$x};


      1. maximw
        10.06.2015 15:07

        Можно даже без {}

        <?php
        $x = 'key';
        $key = 'value';
        $value = 'we need to go deeper';
        echo $$$x; 


        1. GEOgraf
          10.06.2015 15:10

          Забавно)) незнал, спасибо.


  1. NaN
    12.06.2015 15:36
    +1

    $x = 0;
    $y = '  ';
    //$y = '.';
    //$y = '-';
    //$y = '0x0';
    //$y = 'sdfhsdkhfkjsdhfdksj';
    $x == $y; //true
    


    Всё же более наглядно. А то некоторые могут интерпретировать, что $y='x' это отсылка к переменной $x, а не к приведению типа «справа».


  1. Minras
    16.06.2015 10:51
    -1

    Мне РНР напоминает IE — тоже огромная куча милых нелогичных багофич, которые надо просто понять и простить. Причем многие из них мигрируют из версии в версию годами, а иногда фиксятся, и тогда надо помнить не только багофичу и объяснение, почему [было] так, но и версию языка, когда это было.

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


  1. Grusho
    17.06.2015 06:04

    Немного изменил пример с инкрементацией, получилось странно:

    		$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


  1. bolk
    17.06.2015 17:46

    все дело в оптимизации (может быть), но как минимум все дело в том, как хранятся переменные.
    Дело совсем не в оптимизации, а в приоритетах операций и последовательности их обработки.


    1. 0xy Автор
      17.06.2015 18:00

      Спасибо за комментарий! Занесу исправления в текст.


    1. mayorovp
      17.06.2015 19:05

      И все равно более логичным поведение не стало. Обычно приоритеты операций влияют лишь на процесс синтаксического разбора кода — но не на порядок выполнения.


      1. Fesor
        17.06.2015 19:11

        но не на порядок выполнения.

        2 + 2 * 2 =?


        1. mayorovp
          17.06.2015 19:15

          Выполняем синтаксический разбор в соответствии с приоритетами операций:

             +
            /  2   *
              /    2   2
          


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