Глубокое копирование массива в PHP: мифы и реальность
Представьте ситуацию: вы пишете скрипт, который обрабатывает данные из базы. Вы получаете массив пользователей, делаете с ним что-то, а затем внезапно обнаруживаете, что исходные данные изменились. Знакомо? Если вы работаете с PHP, эта проблема может застать врасплох даже опытного разработчика.
Вопрос «Is there a function to make a copy of a PHP array to another?» на Stack Overflow набрал десятки тысяч просмотров. И это не случайно. На первый взгляд, кажется, что копировать массивы в PHP — это тривиальная задача. Но дьявол, как всегда, кроется в деталях.
В этой статье мы разберём все возможные способы копирования массива в PHP: от простого присваивания до работы с многомерными структурами. Вы узнаете, когда PHP создаёт реальную копию, а когда — лишь ссылку на данные. Поехали!
Прямое присваивание: первый сюрприз
Самый интуитивный способ — просто присвоить массив другой переменной:
$original = ['apple', 'banana', 'cherry'];
$copy = $original;
$copy[0] = 'mango';
print_r($original);
// Array ( [0] => apple [1] => banana [2] => cherry )
print_r($copy);
// Array ( [0] => mango [1] => banana [2] => cherry )
Удивительно, но это работает! В PHP массивы — это value types (типы значений), а не ссылочные типы. Когда вы присваиваете массив другой переменной, PHP создаёт поверхностную копию (shallow copy) с отложенным копированием (copy-on-write).
Простыми словами: пока вы не меняете ни один из массивов, они указывают на одни и те же данные в памяти. Как только вы пытаетесь изменить элемент в одной из переменных, PHP выделяет новую память и копирует данные. Это очень эффективно и не требует лишних действий.
Вывод: для простых одномерных массивов прямое присваивание — это полноценное копирование.
Подводный камень: объекты внутри массива
А теперь представьте, что в массиве лежат объекты:
class User {
public $name;
public function __construct($name) {
$this->name = $name;
}
}
$original = [new User('Alice'), new User('Bob')];
$copy = $original;
$copy[0]->name = 'Charlie';
print_r($original[0]->name); // Charlie
Вот он, сюрприз! Поверхностная копия скопировала ссылку на объект, а не сам объект. Изменяя объект в копии, вы меняете его в оригинале. Для объектов нужен другой подход.
Функция array_merge(): неочевидные нюансы
Часто можно встретить совет использовать array_merge() для копирования массива:
$original = [1, 2, 3];
$copy = array_merge([], $original);
$copy[0] = 100;
print_r($original); // [1, 2, 3]
Да, это работает. Но есть важный нюанс: array_merge() сбрасывает числовые ключи и перенумеровывает их с нуля. Если ваши ключи — строки, то всё в порядке. А если числовые — будьте готовы к сюрпризам:
$original = ['a' => 1, 2 => 'two', 5 => 'five'];
$copy = array_merge([], $original);
print_r($copy);
// Array ( [a] => 1 [0] => two [1] => five )
Видите? Ключи 2 и 5 превратились в 0 и 1. Это может поломать логику, если порядок ключей важен.
Вывод: array_merge() подходит только для копирования массивов со строковыми ключами или когда переиндексация не страшна.
Копирование с помощью цикла: ручное управление
Самый надёжный, но и самый многословный способ — пройтись по массиву циклом:
$original = ['x' => 10, 'y' => 20];
$copy = [];
foreach ($original as $key => $value) {
$copy[$key] = $value;
}
$copy['x'] = 100;
print_r($original['x']); // 10
Для простых массивов это избыточно. Но если вам нужно сделать глубокое копирование (deep copy), то без рекурсивного цикла не обойтись:
function deepCopy($array) {
$result = [];
foreach ($array as $key => $value) {
if (is_array($value)) {
$result[$key] = deepCopy($value);
} elseif (is_object($value)) {
$result[$key] = clone $value; // Клонируем объект
} else {
$result[$key] = $value;
}
}
return $result;
}
$original = [
'user' => new User('Alice'),
'hobbies' => ['reading', 'coding']
];
$copy = deepCopy($original);
$copy['user']->name = 'Bob';
print_r($original['user']->name); // Alice — оригинал не изменился!
Этот подход даёт полный контроль, но требует написания кода. Для стандартных задач лучше использовать готовые решения.
Оператор spread (PHP 7.4+)
Начиная с PHP 7.4, можно использовать оператор распаковки ... для копирования массива:
$original = ['a', 'b', 'c'];
$copy = [...$original];
$copy[0] = 'z';
print_r($original); // ['a', 'b', 'c']
Это работает аналогично прямому присваиванию — создаётся поверхностная копия. Для объектов внутри массива действует то же правило: ссылки копируются, а не объекты.
Преимущество spread-оператора — лаконичность и читаемость. Недостаток — не работает с ассоциативными массивами, у которых есть строковые ключи (на самом деле работает, но если ключи повторяются — будет перезапись).
array_slice(): ещё один способ
Функция array_slice() без указания длины возвращает копию всего массива:
$original = [10, 20, 30];
$copy = array_slice($original, 0);
$copy[0] = 99;
print_r($original); // [10, 20, 30]
Этот метод тоже делает поверхностную копию. Он сохраняет числовые ключи, но для ассоциативных массивов со строковыми ключами поведение может отличаться.
Когда использовать: когда нужно скопировать часть массива или когда вы уже используете array_slice() для других целей.
Клонирование объектов: ключ к глубокой копии
Если ваш массив содержит объекты, и вы хотите скопировать их полностью, нужно использовать clone:
class User {
public $name;
public $data;
public function __construct($name, $data = []) {
$this->name = $name;
$this->data = $data;
}
//