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]
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]
Один тип `array` покрывает list, associative array и смешанные структуры; различие задаётся ключами.
Quick recall
Почему в PHP фраза «массив» может быть неточной, если не уточнить структуру ключей?

Ключи: только 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' лучше явно держать строковый формат и не смешивать его с числами.

Quick recall
Какие типы ключей реально может хранить PHP array?

Перезапись не добавляет новый элемент

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

<?php

$status = [
    'new' => 'Новая',
    'paid' => 'Оплачена',
];

$status['new'] = 'Создана';
$status['cancelled'] = 'Отменена';

foreach ($status as $code => $label) {
    echo "$code: $label\n";
}

Вывод:

new: Создана
paid: Оплачена
cancelled: Отменена

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

Quick recall
Что меняется в порядке обхода, если присвоить новое значение уже существующему ключу массива?

[] и следующий 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 и 1

array_reduce() сворачивает массив в одно значение: сумму, индекс, строку, объект результата. Для сложной логики иногда обычный foreach читается лучше, особенно если нужно обрабатывать ключи, early return или исключения. Это не вопрос «функционально или неправильно», а вопрос ясного контракта.

Частые ловушки

Первая ловушка — считать PHP array обычным плотным массивом из C, Java или JavaScript. В PHP это ordered map, и ключи имеют собственные правила.

Вторая — забывать про key casting. '8' и 8 совпадут, а '08' — нет. true станет 1, false0, null''.

Третья — думать, что unset() переиндексирует список. Не переиндексирует. После удаления используйте array_values(), если дальше нужен list.

Четвёртая — выбирать array_map(), array_filter(), array_reduce(), array_merge(), array_replace() и оператор + на автопилоте. У них разные правила ключей. Когда ключи важны, сначала проверьте поведение на маленьком примере.

Пятая — хранить в одном массиве и список, и запись. Если ключи частично числовые, частично смысловые, это сигнал пересмотреть структуру: возможно, нужны два массива или объект. В ООП-коде такой переход естественно ведёт к Классы, объекты и видимость, а для коллекционного обхода — к SPL, итераторы и коллекции.

См. также

Sources

  1. PHP Manual: Arrays
  2. PHP Manual: array_is_list
  3. PHP Manual: array_keys
  4. PHP Manual: array_map
  5. PHP Manual: array_filter
  6. PHP Manual: array_reduce
  7. PHP Manual: Type Juggling
  8. PHP Manual: PHP 7.1 New Features