array в PHP — не только «массив»
В PHP array — это ordered map: упорядоченное отображение ключей на значения. Один и тот же тип используется как список, словарь, стек, очередь, таблица настроек, дерево вложенных данных и результат SQL-запроса. Поэтому фраза «массив в PHP» часто обманчива: под ней может скрываться и список товаров, и ассоциативная запись пользователя.
<?php
$user = [
'id' => 42,
'email' => 'anna@example.com',
'roles' => ['admin', 'editor'],
];
$ids = [10, 11, 12];Оба значения имеют тип array, но их контракты разные. $ids — list: элементы идут под ключами 0, 1, 2. $user — associative array: ключи имеют смысловые имена. Это различие важно для foreach, JSON, валидации входа и функций семейства array_*. Подробнее про обход в циклах см. Операторы и управляющие конструкции, а про JSON-представление — JSON, сериализация и форматы данных.
flowchart TD
A[PHP array] --> B[ordered map]
B --> C[int keys]
B --> D[string keys]
C --> E[list: 0..n-1]
D --> F[associative array]
B --> G[mixed keys: допустимо, но часто пахнет плохим контрактом]
E --> H[array_is_list true]
F --> I[array_is_list false]Ключи: только int и string
Ключ массива в PHP может быть только int или string. Значение — любого типа: строка, число, объект, другой массив, null, callable и так далее. Если ключ записан другим типом, PHP пытается привести его к допустимому виду.
<?php
$items = [
1 => 'int one',
'1' => 'string one',
1.8 => 'float one',
true => 'bool true',
'01' => 'string 01',
null => 'null key',
];
var_export($items);Результат не будет содержать четыре разных элемента с ключом «один». Ключи 1, '1', 1.8 и true приводятся к integer 1, поэтому значения последовательно перезаписываются. Строка '01' остаётся строковым ключом, потому что это не валидная десятичная integer-строка в правилах ключей массива. null становится пустой строкой ''.
Практический вывод простой: не используйте «почти числовые» строки как ключи, если важна точная идентичность. Для внешних ID, артикулов, кодов регионов и значений вроде '00123' лучше явно держать строковый формат и не смешивать его с числами.
Перезапись не добавляет новый элемент
Если ключ уже есть, присваивание меняет значение по этому ключу. Новый элемент в порядок обхода не вставляется.
<?php
$status = [
'new' => 'Новая',
'paid' => 'Оплачена',
];
$status['new'] = 'Создана';
$status['cancelled'] = 'Отменена';
foreach ($status as $code => $label) {
echo "$code: $label\n";
}Вывод:
new: Создана
paid: Оплачена
cancelled: ОтмененаЭто поведение удобно для конфигов: базовый массив можно дополнить или частично заменить. Но оно же создаёт тихие баги, когда ключи неожиданно совпали после приведения типов.
[] и следующий integer-ключ
Запись $array[] = $value добавляет элемент с автоматически выбранным integer-ключом. Обычно это следующий ключ после максимального integer-ключа, который уже встречался в массиве.
<?php
$a = [5 => 'first'];
$a[] = 'second';
print_r($a);Вывод:
Array
(
[5] => first
[6] => second
)unset() удаляет пару ключ-значение, но не «сжимает» список обратно.
<?php
$ids = [10, 11, 12];
unset($ids[1]);
$ids[] = 13;
print_r($ids);Получится не [10, 12, 13], а массив с ключами 0, 2, 3. Если после фильтрации нужен именно list, используйте array_values().
<?php
$ids = array_values($ids);Нюанс версий: начиная с PHP 8.3, если последним максимальным integer-ключом был отрицательный ключ вроде -5, следующий автоматический ключ будет -4. В более старых версиях такой append переходил к 0. Это редко нужно в бизнес-коде, но важно при чтении легаси и библиотек.
List и associative array
С PHP 8.1 есть array_is_list(): она возвращает true, если ключи массива — это последовательные числа от 0 до count($array) - 1 в правильном порядке. Пустой массив тоже считается list.
<?php
var_dump(array_is_list(['a', 'b']));
var_dump(array_is_list([0 => 'a', 2 => 'b']));
var_dump(array_is_list(['id' => 1, 'name' => 'Anna']));Вывод:
bool(true)
bool(false)
bool(false)Это не просто косметика. List безопасно воспринимать как «набор элементов», который можно мапить, фильтровать, отправлять в JSON-массив и отображать таблицей. Associative array чаще означает запись, словарь, индекс по ID или конфиг. Смешанные массивы вроде [0 => 'a', 'name' => 'Anna'] технически допустимы, но почти всегда хуже читаются и хуже сериализуются.
Деструктуризация массивов
Деструктуризация позволяет разобрать массив в переменные. Для list она похожа на позиционное присваивание.
<?php
[$id, $email] = [42, 'anna@example.com'];
echo $id . ': ' . $email . PHP_EOL;Для associative array лучше использовать ключи явно:
<?php
$row = [
'id' => 42,
'email' => 'anna@example.com',
'active' => true,
];
['id' => $id, 'email' => $email] = $row;Так код не зависит от порядка полей. Это особенно полезно рядом с PDO-результатами, DTO-подготовкой и входными структурами. Но деструктуризация не заменяет проверку данных: если ключа нет, вы получите предупреждение об undefined array key и null-подобное поведение в зависимости от контекста. На границе HTTP-ввода всё равно нужна фильтрация; см. GET, POST и фильтрация ввода.
Основные array_* функции
array_keys() возвращает ключи, array_values() — значения с переиндексацией. Эти две функции часто используют после фильтрации или перед сравнением структуры.
<?php
$prices = [
'book' => 1200,
'pen' => 80,
'bag' => 3500,
];
print_r(array_keys($prices));
print_r(array_values($prices));array_map() применяет callable к элементам. Если передан один массив, ключи сохраняются; если массивов несколько, результат получает последовательные integer-ключи.
<?php
$pricesWithTax = array_map(
fn (int $price): int => (int) round($price * 1.12),
$prices,
);array_filter() оставляет элементы, для которых callback вернул true, и сохраняет ключи. Из-за этого после фильтрации list может перестать быть list.
<?php
$numbers = [10, 11, 12, 13];
$even = array_filter($numbers, fn (int $n): bool => $n % 2 === 0);
print_r($even); // ключи 0 и 2
print_r(array_values($even)); // ключи 0 и 1array_reduce() сворачивает массив в одно значение: сумму, индекс, строку, объект результата. Для сложной логики иногда обычный foreach читается лучше, особенно если нужно обрабатывать ключи, early return или исключения. Это не вопрос «функционально или неправильно», а вопрос ясного контракта.
Частые ловушки
Первая ловушка — считать PHP array обычным плотным массивом из C, Java или JavaScript. В PHP это ordered map, и ключи имеют собственные правила.
Вторая — забывать про key casting. '8' и 8 совпадут, а '08' — нет. true станет 1, false — 0, null — ''.
Третья — думать, что unset() переиндексирует список. Не переиндексирует. После удаления используйте array_values(), если дальше нужен list.
Четвёртая — выбирать array_map(), array_filter(), array_reduce(), array_merge(), array_replace() и оператор + на автопилоте. У них разные правила ключей. Когда ключи важны, сначала проверьте поведение на маленьком примере.
Пятая — хранить в одном массиве и список, и запись. Если ключи частично числовые, частично смысловые, это сигнал пересмотреть структуру: возможно, нужны два массива или объект. В ООП-коде такой переход естественно ведёт к Классы, объекты и видимость, а для коллекционного обхода — к SPL, итераторы и коллекции.
См. также
- Операторы и управляющие конструкции —
foreach, условия, оператор+для массивов и обход ordered map. - JSON, сериализация и форматы данных — как list и associative array превращаются в JSON.
- Функции, замыкания и callable — callbacks для
array_map(),array_filter()иarray_reduce(). - Ошибки, исключения и Throwable — warnings при undefined array key и обработка ошибок на границах данных.
- SPL, итераторы и коллекции — когда вместо массива удобнее
Iterator,ArrayObjectили специализированная структура. - Генераторы и Fiber — ленивые потоки значений, которые часто заменяют большие временные массивы.
- GET, POST и фильтрация ввода — почему массивы из внешнего ввода нельзя считать уже корректными структурами.