Перейти к содержимому
Теория · Классы и объекты · урок 6 из 11

Переменная хранит идентификатор объекта

Как вы уже знаете, $b = $a копирует массив целиком: запись в один массив не видна в другом. Давайте проверим, что делает то же присваивание с объектом:

PHP
<?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 из 7 · рамок: 1

new строит объект Point#1, а в $a попадает его идентификатор.

Следите за номерами объектов в рамке {main}

Программа напечатала 5 5 и 9. $a и $b — два имени объекта Point#1, поэтому запись через $b видна через $a. clone $a построил объект Point#2 со своей копией свойства x, и дальше копия живёт отдельно.

Этот вопрос часто задают на собеседовании: «Объекты в PHP передаются по ссылке?» Ответ — нет:

  • переменная хранит идентификатор объекта, и присваивание или передача в функцию копируют этот идентификатор;
  • поэтому функция, получившая объект, меняет тот же объект, что лежит у вызывающего кода;
  • ссылка & — другое: она делает параметр вторым именем самой переменной.

=== и == для объектов

Оператор === для объектов смотрит на идентификатор, а == сравнивает класс и свойства:

PHP
$c = clone $a;
var_dump($a === $b); // bool(true)
var_dump($a === $c); // bool(false)
var_dump($a == $c); // bool(true)

Что здесь происходит:

  1. $a === $b даёт true: это один и тот же объект.
  2. $a === $c даёт false: копия — другой объект, хотя значения в нём те же.
  3. $a == $c даёт true: нестрогое сравнение проверяет, что объекты одного класса и их свойства равны.

clone копирует поверхностно

Здесь картинка с домами требует оговорки: clone строит второй дом, но мебель в двух домах общая. Если в свойстве лежит другой объект, clone копирует его идентификатор, а не сам объект:

PHP
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.

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

Перед изменением объекта «сохраняют копию» обычным присваиванием:

PHP
$backup = $a; // ✗
$a->x = 0;
echo $backup->x, "\n"; // 0

Сообщений нет, но «копия» изменилась вместе с оригиналом: $backup — второе имя того же объекта. Копию делают через clone: после $backup = clone $a; значение $backup->x остаётся 5.

Главное

  • Переменная с объектом хранит идентификатор объекта, а не сам объект.
  • $b = $a копирует идентификатор: два имени ведут к одному объекту, и запись через одно видна через другое.
  • clone строит второй объект, но вложенные объекты в нём остаются общими.
  • === даёт true только для одного и того же объекта, а == сравнивает класс и свойства.