PHP 8.5: clone with для value-объекта статуса заказа (Bitrix)
Иммутабельный DTO статуса заказа с clone with (PHP 8.5): клонирование с подменой полей без методов withX(). По RFC clone with.
Как использовать
- Объявите readonly-класс с конструктором и при необходимости методами-обёртками: return clone $this with ['property' => value];.
- Синтаксис: clone $object with ['propertyName' => newValue]. Подходит для DTO и value-объектов (статусы заказов, стадии сделок).
- Исходный объект не изменяется; возвращается новый экземпляр с подставленным полем. 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.
Проверка
- Иммутабельность — после вызова withLabel() исходный объект не должен измениться:
$status = new OrderStatusDto('P', 'Оплачен', false);
$ru = $status->withLabel('Оплачен (RU)');
assert($status->label === 'Оплачен');
assert($ru->label === 'Оплачен (RU)');
-
PHP 8.5 — синтаксис
clone $obj with [...]работает только в PHP 8.5+. На старых версиях будет синтаксическая ошибка. Проверьте:php -v. -
Чтение после 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.