Интересное в PHP: ловушка со строковыми ключами массива и вызовом array_shift

Метки: ,

Как вы думаете, что напечатает следующий код?

// Заполняем массив (значения такие же, как и ключи)
foreach(array('07', '08', '09', '10') as $n){
    $a[$n] = $n;
}

// Убираем первый элемент массива
array_shift($a);

// Выводим значение элемента с ключом '10'
echo $a['10'];

Если вам, как и мне, кажется, что в элементе массива $a с ключом '10' должна быть строка со значением "10", то вы, к сожалению, неправы. Зато теперь имеет смысл читать эту статью дальше.

На самом деле, к моменту исполнения оператора 'echo', ключа '10' уже не будет в массиве. Об этом нам и сообщит PHP при отработке скрипта (только если бит E_NOTICE активен в error_reporting):

Notice: Undefined index: 10 in E:\string-keys-caveat.php on line 8

Куда же делся нужный нам элемент? Давайте, с помощью var_dump($a), посмотрим на содержимое массива и попробуем понять что произошло.

Вот что мы имеем в массиве $a после вызова array_shift($a):

array(3) {
 ["08"] => string(2) "08"
 ["09"] => string(2) "09"
 [0]    => string(2) "10"
}

Ключа '10' действительно нет. Зато, значение "10" есть в элементе с ключом '0' и как раз это значение мы ожидали увидеть напечатанным при исполнении echo $a['10']. Поскольку значения элементов массива у нас были уникальными и эквивалентными ключам, то можно заключить, что ключ '0', это бывший ключ '10'.

Хорошо, но сразу же после цикла ключ '10' просто обязан быть в массиве, правда? Сейчас запустим var_dump($a) сразу же после foreach, но до array_shift и проверим:

array(4) {
 ["07"] => string(2) "07"
 ["08"] => string(2) "08"
 ["09"] => string(2) "09"
 [10]   => string(2) "10"
}

Верно, искомый ключ на месте, однако одной чертой он отличается от остальных ключей массива. Все ключи строковые, а он — нет. Хотя мы и передавали "10" как строку, PHP сконвертировал её в число. Это случилось потому, что "10" это стандартное представление числа, в отличии от "07", "08" и "09". Об этом даже в документации говорится:

Key может быть либо integer, либо string. Если ключ - это стандартное представление integer, он так и будет интерпретироваться (т.е. "8" будет восприниматься как 8, тогда как "08" будет интерпретироваться как "08").

http://www.php.net/manual/ru/language.types.array.php

Обычно, это преобразование остаётся незамеченным, ведь к числовым ключам можно обращаться так же как и к строковым (echo $a['10']) и программист остаётся в неведении о существовании этой ловушки, которая срабатывает только если массив будет неявно переиндексирован. Как раз последнее и случается при вызове функции array_shift — она не просто удаляет первый элемент массива, но и изменяет числовые ключи массива так, чтобы они шли по-порядку (т.е. "0, 1, 2...").  Потому-то у нас после вызова array_shift($a) элемент массива '10' стал элементом '0', ведь он был единственным элементом с числовым ключом.

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

PS: здесь, правда, остаётся один вопрос — как же заставить строку похожую на число остаться именно строкой в качестве ключа массива (никакие $a[(string) "10"] = 'hi!' не помогают). Понимаю, что такое не часто бывает нужно, а когда нужно, то можно найти другие решения, но интересно жутко.


24 Январь 2012

Комментарии (заморожены на какое-то время)

На этой странице еще нет комментариев.


Интернет реклама