Безумный PHP. Фьюри код

от автора

Сборник 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

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

Когда у нас код вида ($b + $b++), то выходит следющее:
1. сначала вычисляем правый операнд: вернуть 1 и увеличить на 1 $b

$b + 1, $b += 1 

2. взять левый операнд $b (2) b и прибавить к нему вычисленное значение правого

$b = 2 2 + 1 = 3 

А теперь 2й пример.

$a + $a + $a++ 

Так как это сложение, то берем по 2 операнда слева на право.

  1. Взяли $a + $a
  2. Если $a = 1, то берем их значения и вычисляем результат: 1 + 1 = 2
  3. В ячейку кладем 2, и смотрим что дальше: 2 + $a++
  4. Ок, берем значение правого операнда(1) и добавляем его к левому: 2 + 1 = 3
  5. Вроде бы все логично…

Но на деле не логично. Так что не ломайте голову, все работает правильно. Но не так, как вы ожидали. В других языках, к примеру, 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 < 10; $i++) {     echo $s++, "\n"; }   //EOF// 

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

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

Ссылки в 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 и думает что это число. А ему пох на то, что там строка, если что. Так что код может быть годный, но пользуемся проверкой не только по значению, но и по типу. Иначе беда.

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 патронов.

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

Кстати, у нас в компании весело! Приходите к нам, в Tutu.ru за новыми возможностями, их есть у нас 😉

ссылка на оригинал статьи http://habrahabr.ru/post/259865/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *