Перейти к содержимому
Теория · Массивы · урок 4 из 8

Какие бывают ключи

Бирки в гардеробе PHP бывают только двух видов: целое число или строка. Номерок 8 и бирка с именем 'anna' — обычные ключи. А что будет, если повесить вещь под биркой '8' — строкой из одной цифры?

Давайте посмотрим, какие бирки PHP повесит на самом деле:

PHP
<?php

$seats = [];
$seats['8'] = 'Anna';
$seats[8] = 'Boris';
$seats['08'] = 'Vera';
echo count($seats), ' ', $seats['8'], "\n"; // 2 Boris
echo implode(' ', array_keys($seats)), "\n"; // 8 08

Разберём по строкам:

  1. $seats['8'] = 'Anna'; пишет в ключ 8: строку '8' PHP превращает в число.
  2. $seats[8] = 'Boris'; пишет в тот же ключ 8 и заменяет Анну Борисом.
  3. $seats['08'] = 'Vera'; создаёт новый ключ: строка '08' остаётся строкой.
  4. count даёт 2, а $seats['8'] читает ключ 8 и печатает Boris.
  5. Функция array_keys возвращает список всех ключей массива, а echo печатает его через implode: 8 08.

Во что превращается ключ

Ключом может быть только целое число или строка. PHP приводит значение другого типа к одному из них, а некоторые строки — к числу.

Строка с записью целого числа

Если строка выглядит ровно так, как PHP пишет целое число, она становится числом: '8' превращается в 8, '-3' — в -3. Поэтому $a['8'] и $a[8] — один и тот же ключ.

Остальные строки

Все остальные строки остаются строками, даже если похожи на число:

  • '08' — с ведущим нулём;
  • '+8' — со знаком плюс;
  • '8.5' — с дробной частью;
  • ' 8' — с пробелом впереди.

true, false, дробь и null

Значения других типов PHP приводит так:

  • true становится ключом 1, а false — ключом 0;
  • дробное число теряет дробную часть: $a[8.7] пишет в ключ 8 и выдаёт Deprecated: Implicit conversion from float 8.7 to int loses precision;
  • null становится пустой строкой '', а с PHP 8.5 такая запись тоже выдаёт Deprecated.

Deprecated — уведомление: так писать не стоит, и будущая версия PHP может это запретить. Но пока скрипт работает дальше. Дробные и null-ключи лучше не использовать вовсе.

Следующий индекс

Как вы уже знаете, $a[] = … вешает элемент на крючок после самого большого номера. Уточним правило: PHP берёт самый большой целый ключ из всех, что были в массиве, и прибавляет к нему единицу.

PHP
$queue = [10, 20, 30];
unset($queue[2]);
$queue[] = 40;
echo implode(' ', array_keys($queue)), "\n"; // 0 1 3
$codes = ['x' => 5];
$codes[] = 6;
echo implode(' ', array_keys($codes)), "\n"; // x 0

Разберём оба случая:

  1. После unset($queue[2]) ключа 2 нет, но PHP помнит, что он был. Поэтому 40 получает ключ 3, а не 2.
  2. В $codes нет ни одного целого ключа, и первый [] получает ключ 0.

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

Если самый большой целый ключ отрицательный, например -5, следующий получит -4. Так PHP считает начиная с версии 8.3.

Ключи и значения отдельно

array_keys

array_keys($queue) возвращает новый список из ключей массива: [0, 1, 3].

array_values

array_values($queue) возвращает новый список из значений, пронумерованный заново с 0: [10, 20, 40]. Так убирают дыры в номерах после unset.

array_is_list

array_is_list($queue) проверяет, список ли это: ключи должны идти подряд 0, 1, 2 и так далее.

PHP
$values = array_values($queue);
echo implode(' ', array_keys($values)), "\n"; // 0 1 2
var_dump(array_is_list($queue)); // bool(false)
var_dump(array_is_list($values)); // bool(true)

В $queue ключи 0, 1 и 3, поэтому это уже не список. После array_values ключи снова идут подряд.

Один тип под капотом

На собеседовании часто спрашивают, чем индексный массив отличается от ассоциативного под капотом. Ответ: для вашего кода — ничем, тип массива в PHP один. Его устройство называют упорядоченной хеш-таблицей: это таблица, которая быстро находит значение по ключу и помнит порядок добавления.

Списком массив делают только его ключи 0..n-1, то есть от 0 до числа элементов минус один, и array_is_list проверяет именно их. Из этого следует всё, что вы видели в теме:

  • ключи — только int или string, и '8' с 8 — один ключ;
  • порядок обхода — порядок добавления;
  • unset номера не сдвигает: после удаления из начала или середины в номерах остаётся дыра.

Частая ошибка

Из списка удалили элемент, а потом прошли по нему циклом for с индексом:

PHP
$tasks = ['wash', 'cook', 'read'];
unset($tasks[0]);
for ($i = 0; $i < count($tasks); $i++) {
    echo $tasks[$i], "\n"; // ✗
}
TEXT
Warning: Undefined array key 0

После unset в списке ключи 1 и 2, а count равен 2. Цикл проходит $i от 0 до 1: на $tasks[0] PHP выдаёт предупреждение и печатает пустую строку, а потом cook. Задача read под ключом 2 не печатается вовсе. Обходите такой массив через foreach или перенумеруйте его: $tasks = array_values($tasks);.

Главное

  • Ключ массива — int или string: '8' становится 8, true — 1, а '08' остаётся строкой.
  • $a[] берёт самый большой целый ключ из всех, что были в массиве, плюс один; unset номер не освобождает.
  • array_keys и array_values возвращают ключи и значения новыми списками, а array_is_list проверяет, что ключи идут подряд с 0.
  • Тип массива в PHP один — упорядоченная хеш-таблица; списком массив делают только ключи 0..n-1.