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

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions