Как правильно десериализовать объект Request в DTO в Symfony, а затем преобразовать его в сущность?

Здравствуйте! У меня возник вопрос о том, как оптимальным образом преобразовать Request в DTO, а затем в сущность, используя доступные инструменты, код и архитектуру. 

Допустим, у нас есть запрос с такими параметрами: 
<pre><code>{
"name": "Vasya",
"age": 18,
"town": 1
}</code></pre> 

При этом наш DTO имеет следующие свойства: 
<pre><code>string $name;
int $age;
App\Entity\Town $town;</code></pre> 

А также у нас есть сущность User, которая включает в себя некоторые из этих свойств. 

Интересно узнать ваше мнение по следующим пунктам: 

1. Как правильно перенести параметры запроса из объекта Request в DTO? Если с простыми типами данных этот процесс ясен, то как следует поступать с объектами, представленными как свойства (например, как преобразовать значение "town": 1 в $townRepository->find(1))?

2. Что лучше: использовать собственный маппер для переноса данных из DTO в сущность, или просто присваивать значения в контроллере с помощью типа $user->setAge($DTO->getAge());?

Меня интересует ваш взгляд на эти вопросы с точки зрения лучших практик. Я видел реализации с использованием кастомного ParamConverter и рассматривал вариант добавить обработчик события для обработки Request, но не уверен в правильности такого подхода. Хочу минимизировать вероятность ошибок и сразу следовать верному пути. Понимаю, что существуют различные библиотеки, но мне также интересно разобраться в подводных камнях этого процесса на практике. Буду благодарен за любые рекомендации и советы!
  • 16 сентября 2024 г. 12:33
Ответы на вопрос 2
Здравствуйте!

Давайте по порядку разберем ваш вопрос по десериализации данных из `Request` в DTO, а затем преобразованию их в сущность.

### 1. Десериализация данных из объекта Request в DTO

Чтобы корректно перенести параметры запроса из объекта `Request` в DTO, вам нужно:

- Использовать механизм десериализации. В Symfony вы можете использовать `Symfony\Component\Validator` для валидации данных, а также `Symfony\Component\Serializer` для десериализации.
- Для простых типов данных, таких как строка или число, вы можете использовать `$request->request->get('name')` и другие методы.
- Для сложных объектов, таких как `Town`, вы можете использовать репозиторий, чтобы извлечь объект из базы данных. В данном случае вам действительно понадобится репозиторий для нахождения `Town` по его идентификатору.

Пример кода для создания DTO:

```php
use Symfony\Component\HttpFoundation\Request;
use App\DTO\UserDTO;
use App\Entity\Town;
use Doctrine\ORM\EntityManagerInterface;

public function createUser(Request $request, EntityManagerInterface $entityManager)
{
    $data = json_decode($request->getContent(), true);

    $dto = new UserDTO();
    $dto->setName($data['name']);
    $dto->setAge($data['age']);

    // Получаем объект Town из репозитория
    $town = $entityManager->getRepository(Town::class)->find($data['town']);
    if (!$town) {
        throw new NotFoundHttpException('Town not found');
    }
    $dto->setTown($town);

    return $dto;
}
```

### 2. Конвертация DTO в сущность

Что касается переноса данных из DTO в сущность, есть несколько подходов:

- **Использовать маппер**: Создание отдельного маппера может быть хорошей идеей, если ваша логика преобразования становится сложной или вам нужно использовать её в нескольких местах. Это помогает инкапсулировать логику и облегчает тестирование.

- **Простое присваивание в контроллере**: Если логика преобразования проста, вы можете непосредственно присваивать значения из DTO в сущность в контроллере. Этот метод более очевиден и менее затратен по времени, особенно если он используется в ограниченном количестве мест.

Пример присвоения:

```php
public function createUser(Request $request, EntityManagerInterface $entityManager)
{
    // ... получение DTO как в предыдущем примере

    $user = new User();
    $user->setName($dto->getName());
    $user->setAge($dto->getAge());
    $user->setTown($dto->getTown());

    $entityManager->persist($user);
    $entityManager->flush();
}
```

### Лучшие практики

1. **Использовать валидаторы**: Убедитесь, что вводимые данные корректны. Используйте встроенные валидаторы или создайте свои валидации объекта DTO.

2. **Отделяйте логику**: Попробуйте избегать большой логики в контроллерах. Используйте сервисы или собственные классы для обработки бизнес-логики. Например, создание пользователя может быть вынесено в отдельный сервис.

3. **Обработка ошибок**: Не забывайте обрабатывать ошибки, возникающие в результате поиска объектов в базе данных (например, Town), и возвращайте соответствующие HTTP коды.

4. **Использование ParamConverter**: Вы можете рассмотреть использование ParamConverter для автоматического преобразования входных данных в DTO. Это уменьшит количество кода, однако требует дополнительной настройки.

5. **Используйте события**: Если вам нужно обрабатывать списки или сложные сценарии, используйте события для разделения логики. Например, слушатель события может обрабатывать создание или обновление сущностей.

### Заключение

Ваша задача, как разработчика, заключается в создании понятного и простого в сопровождении кода. Начните с простого решения, а при необходимости вы можете легко улучшить его с помощью паттернов и инструментов. Удачи!
Как человек, который в своё время так переусложнял код, что потом сам не понимал, что и где в проекте происходит, посоветую вам использовать самый лучший принцип на свете - "KISS" 

Поэтому, ничего сложного пока не выдумывайте, поверьте, вы всегда сможете потом всё усложнить)))

Я бы сделал так:


Этот вариант максимально прозрачен и сильно сэкономит вам время при дебаге, потому что вы в два клика сможете найти всю логику.
Похожие вопросы