Зачем и как использовать Объекты передачи данных в Laravel

Data Transfer Object (Объект передачи данных) — паттерн программирования, который, при правильном использовании, может улучшить качество код и удобство его дальнейшего сопровождения. В этой статье мы узнаем, почему нужно использовать DTO и как это сделать в Laravel.

Что такое объект передачи данных?

Объект передачи данных (DTO) используется для передачи данных между уровнями приложения. Это простой объект, который содержит набор данных, обычно в форме свойств или полей, которые представляют определенный объект или концепцию в системе. Основная цель DTO — отделить различные уровни или компоненты, позволяя им взаимодействовать и обмениваться данными без необходимости знать детали реализации друг друга.

Зачем использовать DTO

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

Допустим, у меня есть такой код:

class UserController extends Controller
{
    public function store(Request $request): JsonResponse
    {
        return response()->json([
            $this->service->createUser($request->all()),
            Response::HTTP_CREATED
        ]);
    }
}

И мне нужно изменить метод createUser. Это, в целом, довольно простой код, но если бы я его создавал год назад, то и не вспомнил бы какие данные возвращает метод $request->all().

Я мог бы отрефакторить всё это через класс Custom Request:

class UserController extends Controller
{
    public function store(CreateUserRequest $request): JsonResponse
    {
        return response()->json([
            $this->service->createUser($request->validated()),
            Response::HTTP_CREATED
        ]);
    }
}

Но теперь возникают две другие проблемы:
— валидация теперь связана с HTTP-запросом. Если мне нужно вызвать метод createUser, то мне нужно будет снова вручную провалидировать данные.
— данные маппированы, но поскольку $request->validated() возвращает массив, то не получится принудительно указать тип данных, передаваемых методу createUser.

Если мы применим здесь паттерн DTO, то сможем решить обе вышеописанные проблемы. Будем маппировать и валидировать данные в DTO. И теперь, при вызове метода createUser откуда либо, валидация будет произведена автоматически, нужно просто передать DTO в метод.

Как использовать DTO

Как объяснялось выше, DTO могут быть простыми объектами для маппирования свойств. Таким образом, самой простой реализацией DTO было бы создание следующего класса:

class CreateUserDTO
{
    public function __construct(
        private string $name,
        private string $email,
        private string $username,
        private string $password
    ) {}

    // Add getters, setters, validation
}

Затем создаём свой DTO из Request:

class UserController extends Controller
{
    public function store(Request $request): JsonResponse
    {
        return response()->json([
            $this->service->createUser(new CreateUserDTO(...$request->all())),
            Response::HTTP_CREATED
        ]);
    }
}

В new CreateUserDTO(...$request->all()) мы используем именованные аргументы из PHP 8 для создания экземпляра CreateUserDTO.

Таким образом, мы решаем проблемы, которые мы подробно описали в предыдущем разделе. Теперь у нас качественный код и его легко поддерживать.

Заключение

В этой статье мы узнали, что такое Объекты передачи данных, почему мы должны использовать их в наших приложениях и как их использовать в Laravel .

Если вы хотите начать использовать DTO в своих приложениях, я создал специальный пакет, который предоставляет базовый DTO-класс с валидацией и преобразованием типов, а также artisan-команду для создания DTO.

Автор: Wendell Adriel
Перевод: Алексей Широков

Наш Телеграм-канал — следите за новостями о Laravel.