Сегодня столкнулся с одной тривиальной задачей. Настолько тривиальной, что не смог сразу придумать решение, хотя раньше уже сталкивался с ней, но обошёлся костылями.
Задача заключается в следующем: фронту надо менять данные в базе по одному полю за раз (например, настройки). Можно сделать эндпоинт на каждое поле и получить огромную головную боль, пытаясь написать на всё валидаторы и обвязку. А можно написать один эндпоинт, который принимает айди модели и любые данные для обновления.
В DTO с данными для обновления помечаем каждое поле как nullable и при сохранении обновляем только те данные, которые !== null, то есть, которые реально пришли с фронта.
Готово! Но только до тех пор, пока в настройках не появится необязательное поле, которое само по себе может принимать значение null.
В этом случае уже не получится к типу поля просто прибавить ещё один null, иначе получится что-то странное вроде ?bool|null, не являющееся валидным php.
Я из такой ситуации вышел самым простым способом, добавив пустой класс NotSet и дополнив все типы полей этим типом. То есть, private ?int $value стал private int|null|NotSet $value.
В итоге при обновлении данных использовать нужно только те поля, которые дают false в ответ на $value instanceof NotSet. Таким образом мы точно знаем, что фронту нужно записать именно это значение, даже если это null.
Ещё один момент, при запихивании данных из приходящего json`а в `DTO, необходимо писать следующее — array_key_exists($data, ‘key’) ? $data[‘key’] : new NotSet(), так как $data[‘key’] ?? new NotSet() не даст нужного результата. Не очень красиво, но тут функциональность преобладает над красотой.
А вот были бы у нас дженерики, тогда можно было бы просто забахать private Optional<?int> $value и жизнь была бы прекрасна, но увы 🙁