Skip to content

[Bug] Manager API: isset($data[$field]) пропускает null-значения при сохранении — очистка полей не работает #289

@biz87

Description

@biz87

Проблема

В 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 и т.п.) безопасно приводит null0 через тип. Для строковых полей 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-параметры фильтров: «пустой фильтр = не фильтровать» — корректное поведение. Не трогать.

План работ

  1. Для каждого «среднего» / «низкого» кандидата — открыть соответствующую Vue-форму, проверить наличие очищаемых полей. Если есть — баг подтверждён.
  2. Подтверждённые случаи фиксить одним PR (механически issetarray_key_exists, как в PR fix(api): preserve null in delivery/payment numeric fields on save #287). Не подтверждённые — оставить, чтобы не плодить шум.
  3. Для будущего: задокументировать паттерн «не использовать isset для проверки наличия ключа» в CLAUDE.md или в общем гайде для контроллеров.

Связанное

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No fields configured for Bug.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions