Переменная хранит идентификатор объекта
Как вы уже знаете, $b = $a копирует массив целиком: запись в один массив не видна в другом. Давайте проверим, что делает то же присваивание с объектом:
<?php
class Point
{
public int $x = 1;
}
$a = new Point();
$b = $a;
$b->x = 5;
echo $a->x, "\n"; // 5Запись шла через $b, а изменился и $a. Объект не скопировался: обе переменные ведут к одному и тому же объекту.
Вернёмся к домам: переменная хранит не сам дом, а его адрес. $b = $a переписывает адрес на вторую бумажку, и теперь у вас две бумажки, которые ведут к одному дому. Что бы вы ни поменяли в доме, придя по одной из них, это увидит каждый, кто придёт по другой.
Идентификатор объекта
Идентификатор объекта — номер, по которому PHP находит объект. Именно его хранит переменная. new строит объект и отдаёт его идентификатор, а $b = $a копирует идентификатор, и объект остаётся один.
Отдельную копию объекта делает clone: он строит второй дом по образцу первого. Нажимайте «Следующий шаг» и следите за переменными в рамке {main}. Point#1 и Point#2 — два разных объекта, а одинаковый номер у двух переменных значит, что объект один.
1<?php2 3class Point4{5 public int $x = 1;6}7 8$a = new Point();9$b = $a;10$b->x = 5;11$c = clone $a;12$c->x = 9;13echo $a->x, ' ', $b->x, "\n";14echo $c->x, "\n";
new строит объект Point#1, а в $a попадает его идентификатор.
Программа напечатала 5 5 и 9. $a и $b — два имени объекта Point#1, поэтому запись через $b видна через $a. clone $a построил объект Point#2 со своей копией свойства x, и дальше копия живёт отдельно.
Этот вопрос часто задают на собеседовании: «Объекты в PHP передаются по ссылке?» Ответ — нет:
- переменная хранит идентификатор объекта, и присваивание или передача в функцию копируют этот идентификатор;
- поэтому функция, получившая объект, меняет тот же объект, что лежит у вызывающего кода;
- ссылка
&— другое: она делает параметр вторым именем самой переменной.
=== и == для объектов
Оператор === для объектов смотрит на идентификатор, а == сравнивает класс и свойства:
$c = clone $a;
var_dump($a === $b); // bool(true)
var_dump($a === $c); // bool(false)
var_dump($a == $c); // bool(true)Что здесь происходит:
$a === $bдаётtrue: это один и тот же объект.$a === $cдаётfalse: копия — другой объект, хотя значения в нём те же.$a == $cдаётtrue: нестрогое сравнение проверяет, что объекты одного класса и их свойства равны.
clone копирует поверхностно
Здесь картинка с домами требует оговорки: clone строит второй дом, но мебель в двух домах общая. Если в свойстве лежит другой объект, clone копирует его идентификатор, а не сам объект:
class Sofa
{
public string $color = 'grey';
}
class House
{
public function __construct(public Sofa $sofa) {}
}
$first = new House(new Sofa());
$second = clone $first;
$second->sofa->color = 'red';
echo $first->sofa->color, "\n"; // redПоверхностная копия — копия, в которой свойства скопированы, а вложенные объекты остались общими. Диван перекрасили через $second, и в $first он тоже красный. Если вложенный объект нужно копировать вместе с внешним, в классе объявляют метод __clone: PHP вызывает его у новой копии сразу после clone.
Частая ошибка
Перед изменением объекта «сохраняют копию» обычным присваиванием:
$backup = $a; // ✗
$a->x = 0;
echo $backup->x, "\n"; // 0Сообщений нет, но «копия» изменилась вместе с оригиналом: $backup — второе имя того же объекта. Копию делают через clone: после $backup = clone $a; значение $backup->x остаётся 5.
Главное
- Переменная с объектом хранит идентификатор объекта, а не сам объект.
$b = $aкопирует идентификатор: два имени ведут к одному объекту, и запись через одно видна через другое.cloneстроит второй объект, но вложенные объекты в нём остаются общими.===даётtrueтолько для одного и того же объекта, а==сравнивает класс и свойства.