Какая логика должна быть внутри DTO (Data Transfer Object)?

«Какая логика должна быть внутри DTO (Data Transfer Object)?» — вопрос из категории Архитектура, который задают на 24% собеседований PHP Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

DTO — это простой контейнер для данных, предназначенный только для их передачи между слоями приложения (например, из контроллера в сервис или через API). Внутри DTO не должно быть бизнес-логики.

Что должно быть внутри DTO:

  1. Приватные свойства (поля), представляющие данные.
  2. Конструктор для инициализации всех обязательных полей (часто используется подход "все или ничего").
  3. Геттеры для чтения данных. Сеттеры обычно нежелательны, чтобы сохранить иммутабельность (неизменяемость) объекта после создания.
  4. Базовая валидация данных в конструкторе (проверка типов, обязательных полей, простых инвариантов).

Пример DTO для регистрации пользователя на PHP:

final class UserRegistrationDto
{
    public function __construct(
        private string $email,
        private string $plainPassword,
        private string $name
    ) {
        // Базовая валидация внутри DTO
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            throw new InvalidArgumentException('Invalid email format');
        }
        if (strlen($plainPassword) < 8) {
            throw new InvalidArgumentException('Password must be at least 8 characters long');
        }
    }

    public function getEmail(): string
    {
        return $this->email;
    }

    public function getPlainPassword(): string
    {
        return $this->plainPassword;
    }

    public function getName(): string
    {
        return $this->name;
    }
}

// Использование
$dto = new UserRegistrationDto('user@example.com', 'securePass123', 'John Doe');
// Передаем DTO в сервис для дальнейшей обработки (хэширования пароля, сохранения в БД)
$userService->register($dto);

Чего НЕ должно быть в DTO:

  • Сложной бизнес-логики (расчетов, взаимодействия с БД, внешними API).
  • Аннотаций ORM (как в Doctrine Entity).
  • Методов, изменяющих состояние, кроме конструктора.

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