PHP
#php#php-8-5#bitrix#clone-with#dto#readonly#order#status

PHP 8.5: clone with для value-объекта статуса заказа (Bitrix)

Иммутабельный DTO статуса заказа с clone with (PHP 8.5): клонирование с подменой полей без методов withX(). По RFC clone with.

Как использовать

  1. Объявите readonly-класс с конструктором и при необходимости методами-обёртками: return clone $this with ['property' => value];.
  2. Синтаксис: clone $object with ['propertyName' => newValue]. Подходит для DTO и value-объектов (статусы заказов, стадии сделок).
  3. Исходный объект не изменяется; возвращается новый экземпляр с подставленным полем. PHP 8.5+.

В Bitrix и интеграциях статусы заказов и стадии сделок удобно представлять иммутабельными объектами (readonly): код, подпись, признак «финальный». При смене подписи под язык или кода нужно получить копию с новым полем, не меняя оригинал. Раньше писали методы withLabel(), withCode() вручную. В PHP 8.5 появилась конструкция clone with: clone $object with ["property" => value] — клонирование с подменой свойств, в том числе readonly. Проблема: без clone with код обрастает wither-методами или объект ошибочно мутируют. Симптомы: дублирование кода, путаница с мутабельностью. Ниже — DTO статуса заказа с clone with и примерами по RFC и документации PHP 8.5; проверка и типичные ошибки.

Решение

Конструкция clone with позволяет клонировать объект и сразу задать новые значения свойств (в том числе readonly). Синтаксис: clone $object with ["property" => value]. Удобно для DTO и value-объектов в Bitrix.

<?php

/**
 * Value-объект статуса заказа (PHP 8.5+).
 */
readonly class OrderStatusDto
{
    public function __construct(
        public string $code,
        public string $label,
        public bool $isFinal = false,
    ) {}

    public function withLabel(string $label): self
    {
        return clone $this with ['label' => $label];
    }

    public function withCode(string $code): self
    {
        return clone $this with ['code' => $code];
    }
}

// Исходный объект не меняется
$status = new OrderStatusDto('P', 'Оплачен', false);
$ru = $status->withLabel('Оплачен (RU)');
$en = $status->withLabel('Paid');

Маппинг статуса заказа Bitrix в DTO с локализацией:

$status = new OrderStatusDto(
    $order->getField('STATUS_ID'),
    GetMessage('STATUS_P') ?: 'Оплачен',
    in_array($order->getField('STATUS_ID'), ['F', 'C'], true)
);
$localized = $status->withLabel(GetMessage('STATUS_P_LOCALIZED'));

По RFC clone with: в массиве после with — пары «имя_свойства» => значение. Учитываются видимость и типы. Для readonly-свойств изменение допустимо только в момент clone with.

Проверка

  1. Иммутабельность — после вызова withLabel() исходный объект не должен измениться:
$status = new OrderStatusDto('P', 'Оплачен', false);
$ru = $status->withLabel('Оплачен (RU)');
assert($status->label === 'Оплачен');
assert($ru->label === 'Оплачен (RU)');
  1. PHP 8.5 — синтаксис clone $obj with [...] работает только в PHP 8.5+. На старых версиях будет синтаксическая ошибка. Проверьте: php -v.

  2. Чтение после clone — у нового объекта все поля должны быть доступны; изменено только указанное в массиве with. Проверьте остальные поля (code, isFinal) у результата clone with.

Типичные ошибки

  • Имя свойства в with не совпадает с полем класса — ключ в массиве должен точно совпадать с именем свойства (с учётом регистра). Иначе будет ошибка или свойство не подставится.
  • Попытка изменить readonly без clone with — присвоение readonly-свойству вне конструктора и вне clone with запрещено. Всегда используйте clone with для «новой версии» объекта.
  • PHP меньше 8.5 — clone with добавлен в 8.5. На старых версиях используйте обычные методы withX(), возвращающие new self(…) с подставленными аргументами.

Где применять

  • Bitrix: DTO статусов заказов, стадий сделок, настроек — везде, где нужна иммутабельная структура с «копией с изменённым полем».
  • Интеграции и любой PHP 8.5+: value-объекты, события, конфигурация — без ручных wither-методов под каждое поле.

Связанные сниппеты: Изменить статус заказа D7 и сохранить, match для маппинга статуса заказа, Атрибут Override в PHP 8.3.