Repository navigation
[Bug] Manager API: isset($data[$field]) пропускает null-значения при сохранении — очистка полей не работает #289
Description
Проблема
В Manager API контроллерах распространён паттерн whitelisted-проброса полей из payload в xPDO-объект:
foreach ($allowedFields as $field) {
if (isset($data[$field])) {
$obj->set($field, $data[$field]);
}
}isset($data[$field]) возвращает false для значения null, не только для отсутствующего ключа. На фронте Vue-форм PrimeVue InputNumber (и некоторые другие компоненты) при очистке поля отправляют null в payload. В результате очистка поля не доходит до $obj->set(), и старое значение остаётся в БД. Вода вводимая пользователем как 0 сохраняется корректно (isset(0) = true), а очистка — нет.
Этот баг был подтверждён и пофикшен в DeliveriesController / PaymentsController (PR #287, пример — free_delivery_amount). Сейчас открываю отдельную задачу, чтобы пройтись по остальным контроллерам Manager API с тем же паттерном.
Решение
Заменять isset($data[$field]) на array_key_exists($field, $data). Это корректно отличает «ключа нет в payload» от «ключ есть, но значение null». xPDO для NOT NULL колонок (decimal default 0.0, int default 0 и т.п.) безопасно приводит null → 0 через тип. Для строковых полей null тоже доходит до $obj->set() и сохраняется ожидаемо.
Кандидаты на проверку
Обнаружены 14 вхождений паттерна в 7 контроллерах. Не все из них — реальный баг: проблема возникает только когда соответствующее Vue-поле может реально отправить null (InputNumber с clearButton, Select с show-clear и т.п.). Нужно по каждому посмотреть Vue-форму, есть ли там очищаемые числовые/select-поля.
Высокая вероятность регрессии
OrdersController.php(строки 647, 1149) — заказ имеет числовые поля (cost,cart_cost,delivery_cost,weightи т.д.). Если хоть одно из них доступно для очистки в админке — баг.
Средняя вероятность
CustomerAddressesController.php(95, 144) — поля адресов. Большинство строковые, но могут бытьindex(почтовый индекс), номера квартир/этажей черезInputNumber— нужно проверить.VendorsController.php(170, 206) — вендоры (имя, страна, сайт). Скорее всего строки, но проверить нужно.
Низкая вероятность (но проверить тоже стоит)
CustomersController.php(170) — поля клиента в основном строковые.LinksController.php(154, 191) — связи между товарами, поля категориальные.StatusesController.php(121, 165) — статусы заказа (name,color).
Не баг (отдельный паттерн)
CategoryProductsController.php(89, 97, 121) — тамisset && !== ''. Это GET-параметры фильтров: «пустой фильтр = не фильтровать» — корректное поведение. Не трогать.
План работ
- Для каждого «среднего» / «низкого» кандидата — открыть соответствующую Vue-форму, проверить наличие очищаемых полей. Если есть — баг подтверждён.
- Подтверждённые случаи фиксить одним PR (механически
isset→array_key_exists, как в PR fix(api): preserve null in delivery/payment numeric fields on save #287). Не подтверждённые — оставить, чтобы не плодить шум. - Для будущего: задокументировать паттерн «не использовать
issetдля проверки наличия ключа» вCLAUDE.mdили в общем гайде для контроллеров.
Связанное
- PR fix(api): preserve null in delivery/payment numeric fields on save #287 — исправление того же паттерна в
DeliveriesController/PaymentsController. Кейс был обнаружен по тестированиюfree_delivery_amount(PrimeVueInputNumberпри очистке).
Activity
fix(api): per-field null payload semantics in Manager API (#289) (#310)
release: 1.12.0-beta1