Skip to content

fix(product): improve options parsing and empty options handling#149

Merged
biz87 merged 3 commits intobetafrom
fix/product-options-parsing
Mar 16, 2026
Merged

fix(product): improve options parsing and empty options handling#149
biz87 merged 3 commits intobetafrom
fix/product-options-parsing

Conversation

@Ibochkarev
Copy link
Copy Markdown
Member

@Ibochkarev Ibochkarev commented Mar 16, 2026

Описание

Улучшение парсинга опций товара из полей формы и обработки пустого массива опций.

Изменения:

  • Вынесён парсинг ключей опций в общий метод Utils::extractOptionKey() (устранение дублирования)
  • Использование str_starts_with() вместо strpos() для проверки префикса options- (PHP 8.2+)
  • В Update и UpdateFromGrid всегда вызывается setProperty('options', $options), включая пустой массив — обеспечивает корректную очистку опций через форму
  • Удалена избыточная проверка !is_array($options) в OptionSyncService (тип задан в сигнатуре)
  • Исправлена обработка пустого массива в OptionSyncService: при removeOther=true теперь вызывается removeUnusedOptions()

Тип изменений

  • Исправление бага (non-breaking change)
  • Новая функциональность (non-breaking change)
  • Breaking change (изменение, ломающее обратную совместимость)
  • Рефакторинг (без изменения функциональности)
  • Документация
  • Другое (опишите):

Связанные Issues

#148

Как это было протестировано?

  • Ручное тестирование
  • Автоматические тесты (PHPStan, ESLint)
  • Тестирование на разных версиях PHP/MODX

Чеклист

  • Код соответствует стилю проекта
  • Добавлены/обновлены комментарии в сложных местах
  • Изменения не ломают существующую функциональность
  • Лексиконы добавлены на двух языках (ru/en) — не требуется
  • PHPStan проходит без новых ошибок
  • ESLint проходит без ошибок — не применимо
  • Обновлён CHANGELOG.md — для минорного фикса не требуется

- Extract option key parsing to Utils::extractOptionKey()
- Use str_starts_with() for options-* field detection (PHP 8.2+)
- Always set options property in Update/UpdateFromGrid for form clearing
- Remove redundant is_array check in OptionSyncService
- Handle empty options array with removeUnusedOptions when removeOther=true
@Ibochkarev Ibochkarev marked this pull request as ready for review March 16, 2026 04:36
@Ibochkarev Ibochkarev requested a review from biz87 March 16, 2026 04:36
@Ibochkarev Ibochkarev self-assigned this Mar 16, 2026
Copy link
Copy Markdown
Member

@biz87 biz87 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

Рефакторинг extractOptionKey() — чисто и правильно. Но основная заявленная цель PR (очистка опций через форму) не работает из-за разрыва в цепочке вызовов.


Критичное: фикс не достигает цели

afterSave() в Update.php блокирует пустой массив опций

PR меняет beforeSet() — теперь setProperty('options', $options) вызывается всегда, даже при $options = []. Но afterSave() (строки 113-114) не изменён:

$options = $this->getProperty('options');
if (!empty($options) && is_array($options)) {
    $service->saveOptions($productData, $options, false);
}

!empty([])false. Пустой массив никогда не дойдёт до saveOptions().

Более того, afterSave() вызывает saveOptions() с removeOther = false. Даже если пустой массив доберётся до OptionSyncService::saveProductOptions(), изменение в сервисе (вызов removeUnusedOptions при empty($options) && $removeOther) не сработает, т.к. removeOther всегда false в этом месте.

Цепочка разорвана в двух местах. Заявленная очистка опций через форму не происходит.

То же самое для UpdateFromGrid — наследует afterSave() от Update.


Замечания по extractOptionKey

Метод корректный для основного кейса, но есть edge case:

return rtrim(substr($key, 8), '[]');

rtrim($str, '[]') удаляет символы из набора [, ] с конца строки поочерёдно. Для options-color[]color — ок. Но для гипотетического options- → пустая строка (не null). Стоит добавить проверку:

$optionKey = rtrim(substr($key, 8), '[]');
return $optionKey !== '' ? $optionKey : null;

Итого

  1. Критично: afterSave() в Update.php нужно обновить — заменить !empty($options) на $options !== null (или is_array), и передавать removeOther = true если нужна очистка. Без этого весь фикс OptionSyncService бесполезен.
  2. extractOptionKey — добавить guard на пустую строку
  3. Сам рефакторинг (str_starts_with, вынос в Utils) — хорошо

- Simplify options handling by removing redundant checks for empty options
- Adjust logic to remove other options only when the options array is empty
- Update comments for clarity on options processing
@Ibochkarev Ibochkarev requested a review from biz87 March 16, 2026 07:31
Without the guard, every product save without options-* form fields
triggers afterSave → saveOptions([], removeOther=true) which deletes
all options previously saved by msProductData::save().

The actual fix for #148 is extractOptionKey() with rtrim('[]') —
form sends options-color[] but old substr gave key "color[]" instead
of "color", so the option was never matched/deleted.

- Restore !empty($options) check in beforeSet/afterSave (Update, Create)
- Keep removeOther=false: custom form options must not remove JSON options
- Restore !empty guard in UpdateFromGrid::beforeSet
@biz87 biz87 merged commit 833402b into beta Mar 16, 2026
@Ibochkarev Ibochkarev deleted the fix/product-options-parsing branch March 16, 2026 08:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Не удаляются опции товара в админке

2 participants