Ответ
В PHP публичное свойство по умолчанию мутабельно, поэтому для создания иммутабельного класса его нужно защитить от изменений после создания объекта. Вот как я это делаю:
1. Использование readonly-свойств (PHP 8.1+):
Это самый чистый и современный способ. Свойство можно объявить публичным, но readonly.
class ImmutablePoint {
public function __construct(
public readonly float $x,
public readonly float $y
) {}
// Методы могут возвращать новые экземпляры, а не менять текущий
public function translate(float $dx, float $dy): self {
return new self($this->x + $dx, $this->y + $dy);
}
}
// Использование
$point = new ImmutablePoint(1.5, 2.0);
echo $point->x; // OK: чтение
// $point->x = 3.0; // ФАТАЛЬНАЯ ОШИБКА: нельзя изменить readonly-свойство
$newPoint = $point->translate(1, 1); // Создаем новый объект
2. Классический способ с приватными свойствами и геттерами (для PHP < 8.1):
class ImmutablePoint {
private float $x;
private float $y;
public function __construct(float $x, float $y) {
$this->x = $x;
$this->y = $y;
}
public function getX(): float { return $this->x; }
public function getY(): float { return $this->y; }
// Отсутствие сеттеров (setX, setY) предотвращает изменение.
public function translate(float $dx, float $dy): self {
return new self($this->x + $dx, $this->y + $dy);
}
}
3. Важное замечание про объекты внутри свойств:
Если свойство является объектом, даже readonly защищает только ссылку, но не внутреннее состояние объекта.
class ImmutableContainer {
public function __construct(
public readonly array $items, // Массив можно заменить, но не изменить? Внимание!
public readonly DateTime $date // Объект DateTime мутабелен!
) {}
}
$container = new ImmutableContainer([1, 2], new DateTime());
// $container->items = [3, 4]; // Ошибка: нельзя переназначить свойство.
$container->items[] = 3; // Ошибка? НЕТ! Это изменит массив (если он не был frozen).
$container->date->modify('+1 day'); // Ошибка? НЕТ! Изменяет внутреннее состояние объекта.
Решение: Для полной иммутабельности нужно использовать иммутабельные объекты (например, DateTimeImmutable вместо DateTime), возвращать копии массивов или использовать коллекции, которые не позволяют модификацию.