Тип ключей в PHP

Наткнулись с коллегами на странную вещи в ПХП: тип имени свойства. Непонятно? Давайте посмотрим на код. Как вы думаете, есть ли какая-то разница в этих двух строках кода:
$first = (object) [ 1 => 1 ];
$second = new stdClass(); $second->{1} = 1;
В первом случае массив получает тип «объект» (и в этом случае ПХП создаёт экземпляр класса stdClass, используя ключи и значения массива как имя и значение свойства соответственно), во втором случае сразу создаётся экземпляр класса и его свойству с именем «1» присваивается значение.

Невооружённым взглядом различий не видно, но если попытаться обратиться к этим свойства, то сразу увидим что-то странное:
var_dump(
  $first->{1}, // выведет «Notice: Undefined property: stdClass::$1»
  $second->{1} // выведет значение «1»
);
При этом свойство существует: var_dump обоих объектов его покажет и после преобразования переменной в массив, мы так же получим искомое. Тем не менее у первого объекта нельзя никаким образом обратиться к этому свойству: удалить его, прочитать, перезаписать! Всё проясняет следующий код:
$first = (object) [ 1 => 1 ];
$first->{1} =2;
var_dump($first);

/* у нас получился объект, у которого два свойства с одним именем,
но разными типами(!) имени, у одного он строка, у второго — число

object(stdClass)#1 (2) {
  [1]=>
  int(1)
  ["1"]=>
  int(2)
}
*/

var_dump((array) $first);

/* попытка преобразовать его в массив, приводит к появлению
неожиданного уродца — массива у которого два ключа с одним и тем
же именем, но разными типами…

array(2) {
  [1]=>
  int(1)
  ["1"]=>
  int(2)
}
*/

$arr = [ 1 => 1 ];
$arr["1"] = 2;
var_dump($arr);

/* …который вряд ли можно создать каким-то ещё способом

array(1) {
  [1]=>
  int(2)
}
*/
К свойству с числовым именем нельзя обратиться по имени, у ПХП просто нет конструкций для этого! Тем не менее, var_dump и foreach его видят! У массива всё ровно наоборот — по имени нельзя обратиться к свойству со строчным именем, если есть числовое.

Вот такие неожиданные открытия в ПХП. Я думаю, что это сделано для того, чтобы сохранить тип ключа массива в преобразованиях из объекта и обратно, но это решение теперь даёт странные сторонние эффекты. Например, перебирая такой массив через foreach можно столкнуться с ситуацией, когда удалить свойство из объекта по имени не удаётся. Или при итерации массива по известным ключам, мы будем получать одно и то же значение два раза (возвращаться всегда будет только то, которое записано по числовому имени).

В общем, масса интересных граблей насыпана в этом месте! Счастливой прогулки!
16 апреля 2014 12:42

vasa_c (инкогнито)
16 апреля 2014, 14:10

Прикольно. Не понятно, что значит "сохранить тип ключа массива в преобразованиях из объекта и обратно".

Евгений Степанищев (bolknote.ru)
16 апреля 2014, 14:46, ответ предназначен vasa_c

В преобразованиях вида
$v = [ 1 => 1, "e" => "e" ];
$k = (object) $v;
$d = (array) $k;

они хотели, чтобы тип ключа в массиве сохранился. Т.е. единица тут должна быть числом, а «e» — строкой.

vstebunov@gmail.com (инкогнито)
16 апреля 2014, 15:12

вот еще крутые грабли:
$arr[null] = 3;
$arr[] = 7;
$arr[0] = 9;
print_r($arr);

Морозов (morozov.livejournal.com)
16 апреля 2014, 15:12

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

Сергей Морозов (morozov.livejournal.com)
16 апреля 2014, 15:18

vstebunov, здесь всё по мануалу: NULL приводится к пустой строке. В случае, если ключ не указан, ключом будет {максимальный целочисленный ключ} + 1 или 0, если такого ключа нет. Последнее присваивание явно указывает ключ 0 и переписывает предыдущее значение.

Евгений Степанищев (bolknote.ru)
16 апреля 2014, 15:51, ответ предназначен Сергей Морозов (morozov.livejournal.com):

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

vstebunov@gmail.com (инкогнито)
16 апреля 2014, 17:47

Сергей, ну раз по мануалу так по мануалу.

Евгений Степанищев (bolknote.ru)
16 апреля 2014, 18:56, ответ предназначен vstebunov@gmail.com

Мм… тут кажется понятно всё, нет?

Евгений Степанищев (bolknote.ru)
16 апреля 2014, 19:35

Кстати, Reflection вообще не видит свойств, у которых имя числового типа.

Ваше имя или адрес блога (можно OpenID):

Текст вашего комментария, не HTML:

Кому бы вы хотели ответить (или кликните на его аватару)