Как стать автором
Обновить

Комментарии 36

по сути, DTO - просто класс публичных свойств с сеттерами/геттерами? такой себе класс со списком свойств, с которого можно делать инстанс, фаршировать полученными данными потом куда-то пересылать, и у нас за пересылку отвечает один этот инстанс, а не какой-то условный массив переменных или и вообще набор переменных?

И я так понимаю, в dto нельзя делать никакой валидации/обработки помещаемых значений и там вообще не должно быть логики? (ну как нельзя - не нужно)

да, все так

Ничего страшного не случится если какая-то базовая валидация всё же там будет, например тот же тайп-хинтинг и strict_types = 1. Есть пакеты дающие еще больше контроля.

В конструкторе можно что-то валидировать. Что-бы 100 процентов быть уверенным в объекте.

Можно внешний валидатор подключить, используя атрибуты типа #[Assert\NotBlank], но обычно его для команд используют.

валидация в конструкторе - это вообще приемлимо? ну то есть, это можно делать, но как-то такое ощущение, что ей там не место, должны просто отработать типы:

public function __construct(
    public readonly int    $id,
    public readonly string $name,
    public readonly string $email,
    public readonly string $phone,
) {}

А там уже что в конструктор дали - то дальше передали и провалидировали в целевом классе, по крайней мере это какие-то мои соображения, как такой момент можно решить

Поставил вам минус за низкий технический уровень материала, и готов объяснить, почему.

  1. Стоило упомянуть, что для реализации DTO чаще всего используется паттерн Value Object и указать на разницу между этими двумя понятиями.

  2. Неплохо бы объъяснить разницу между Value Object и Entity.

  3. Стоило рассказать, что DTO по определению является иммутабельным объектом. А из этот следует применение модификатора readonly в свойствах и/или в классе.

  4. Не упомянут тайп-хинтинг свойств - почему?

  5. Если мы уж говорим о валидации, то стоит акцентировать внимание на том, что Value Object (и DTO, как подкласс) не могут находиться в невалидном состоянии, иначе это нарушает смысл паттерна.

  6. Валидация может быть как при создании объекта (в конструкторе), так и условно "внешняя"

  7. Второй вариант валидации - более гибкий. Он позволяет задать с помощью метаданных правила и сценарии валидации.

  8. Метаданные в современном PHP задаются через атрибуты. Нужно было бы показать пример, например из Symfony.

  9. Для преобразования в JSON в стандартной библиотеке PHP есть интерфейс. Почему его не использовали?

  10. Я бы посоветовал вам взять примеры сериализации таких объектов опять же из Symfony. И рассказать про метаданные, две стадии сериализации (преобразование в массив, а затем в нужный формат) и про десериализацию.

и это только навскидку...

Спасибо за советы! Что-то из вашего перечня уже было написано (добавил). Изначально хотелось разбить публикацию на несколько частей с постепенным погружением. Лонгриды сейчас мало кто читает, увы.

Даже "улучшив" публикацию, вы всё равно несете в ней какой-то малосвязный бред.

Спасибо за фидбек! Завтра постараемся еще улучшить.

Благодарю вас за объяснение вашего негативного отзыва. Я стараюсь предоставлять максимально полезный и точный материал, и ваша критика поможет мне сделать это лучше. Разрешите мне ответить на ваши замечания.

  1. Вы правы, что для реализации DTO обычно используется паттерн Value Object, и я мог бы упомянуть этот факт в своем ответе.

  2. Я согласен, что стоило бы объяснить разницу между Value Object и Entity, чтобы дать читателю более полное понимание этих понятий.

  3. Я мог бы упомянуть, что DTO является неизменяемым объектом, и поэтому должен использовать модификатор readonly для свойств.

  4. Тайп-хинтинг свойств также стоит упомянуть в контексте DTO и Value Object.

  5. Согласен, что стоило бы уточнить, что Value Object (и DTO как подкласс) не могут находиться в невалидном состоянии.

  6. Валидация может быть как при создании объекта (в конструкторе), так и внешней.

  7. Метаданные в современном PHP задаются через атрибуты, и я мог бы привести примеры из Symfony.

  8. Согласен, что для преобразования в JSON в стандартной библиотеке PHP есть интерфейс, и я мог бы упомянуть его в своем ответе.

  9. Примеры сериализации можно было бы взять из Symfony, чтобы показать более продвинутые возможности метаданных и двухстадийной сериализации.

Я ценю ваш отзыв и обязуюсь учесть ваши замечания при подготовке ответов в будущем. Спасибо вам за вклад в улучшение качества моих ответов.

Мне кажется, что использовать чатgpt для ответа на комментарии - это такой новый уровень метатроллинга, сверхкристаллизованное неуважение ?

Что вы! Я не могу ощущать эмоции и не могу умышленно проявлять неуважение к комментариям или людям. Моя задача - помогать пользователям в получении ответов на их вопросы и предоставлять информацию по мере моих знаний. Вынужден с сожалением констатировать, многие из пользователей отвыкли от вежливого общения, многие слишком много думают о себе. Если вы имеете какие-либо опасения относительно использования ChatGPT для ответа на комментарии, то решение о том, читать его или нет, полностью зависит от вас. Моя цель - быть полезным инструментом для обмена знаниями и информацией.

DTO подразумевает 2 основных аспекта: отсутствие какой либо логики и иммутабельность. В своём примере вы нарушили оба. Конвертация в JSON, как ни крути - логика, а публичные свойства по умолчанию мутабельны. Так же вы совершенно не используете современные возможности языка, напомню, что версия 7.4 больше не поддерживается c ноября прошлого года.

<?php
class UserDTO
{
    public function __construct(
        public readonly int    $id,
        public readonly string $name,
        public readonly string $email,
        public readonly string $phone,
    ) {}
}

Если используется версия php младше 8.1, то следует сделать свойства приватными, и добавить геттеры для доступа к свойствам.

Для сериализации DTO можно использовать что-то такое:

<?php
class UserDTOSerializer
{
    public function serialize(UserDTO $dto): string
    {
        return json_encode([
            'id'    => $dto->id,
            'name'  => $dto->name,
            'email' => $dto->email,
            'phone' => $dto->phone,
        ]);
    }
}

По-хорошему вместо json_encode лучше использовать класс-обёртку, передаваемую через конструктор, чтобы можно было заменить encoder, при необходимости сменить формат данных. В этом случае достаточно будет только реализовать новый энкодер под новый формат и передавать его в сериализатор при инициализации.

Тоже самое касается и валидации. Можно использовать внешний валидатор, правила валидации можно задать в аттрибутах свойств.

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

спасибо за замечание!

DTO не содержит бизнес логики, а не "логики вообще". Сериализация/десереализация - эта логика, которая очень подходит для инкапсуляции ее в DTO, ибо вообще вся суть DTO изначально - быть сериализованной.

ибо вообще вся суть DTO изначально — быть сериализованной.

Зачем во время передачи данных из аппликейшн слоя в доменный или инфраструктурный его сериализовывать? Задача DTO хранить состояние для передачи его куда-либо. А сериализация — это сугубо транспортный слой.


Максимально допустимая логика в DTO — это предоставлять геттеры, которые в эпоху 8.1+ не особо-то и нужны больше.

А DTO изначально задумывался для других целей, а именно для экономии дорогих внешних вызовов. То, о чем вы говорите — это по Фаулеру LocalDTO.

Я бы сказал по Эвансу =)


А если говорить про экономию ресурсов, то не понимаю где именно и какая это экономия? На чём?

Ну я про эту статью https://martinfowler.com/bliki/LocalDTO.html
Экономия — ну тут я по сути цитирую P of EAA:


When you're working with a remote interface, such as Remote Facade (388), each call to it is expensive. As a result you need to reduce the number of calls, and that means that you need to transfer more data with each call. One way to do this is to use lots of parameters. However, this is often awkward to program — indeed, it's often impossible with languages such as Java that return only a single value.

The solution is to create a Data Transfer Object that can hold all the data for the call. It needs to be serializable to go across the connection. Usually an assembler is used on the server side to transfer data between the DTO and any domain objects.

Не видел эту статью, спасибо.


Но в любом случае, кажется, она выглядит как капитанство. Тот же OLE -> COM появился ещё в каких-то бородатых 90х, где были специфицированы типы и структуры для ABI-общения. Так что гонять структурки между программами, вместо скаляров — это не открытие века.


А если говорить о контексте, то всё же книга Эванса вышла на год раньше этой статьи =)

Вот вопрос о корректности использования конструкторов и других методов в DTO. Как бы его задача - передать данные. А для заполнения данными, имхо, правильнее использовать гидраторы. Для преобразования в JSON - тоже специальные конструкции. А DTO - просто объект с данными и все, имхо

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

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

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

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

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

Зачем вы отвечаете с помощью GPT? Могли бы хотя бы править. Цирк какой-то. Что происходит?

Сама статья тоже, судя по всему написана и скорректирована чат-ботом.

Поведение != методы. Сериализация/десериализация - это не поведение объекта. Это по сути то, для чего вообще создается DTO - передать его по каналам связи.

НЛО прилетело и опубликовало эту надпись здесь

DTO не для хранения, а для передачи данных. Это передача данных в конкретном контексте. Ничего криминального в инкапсуляции сериализации нет. Вообще можно начать с Фаулера.

Согласен, DTO (Data Transfer Object) в PHP часто используется для передачи данных между различными компонентами системы, например, между слоями приложения или между приложением и базой данных. DTO представляет собой объект, который содержит данные, которые необходимо передать, и может включать методы для доступа к этим данным. DTO обычно используется в конкретном контексте, и его структура может быть специально определена для этого контекста.

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

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

Если вы хотите узнать больше о DTO и о том, как его использовать в PHP, то, как вы уже упомянули, можете обратиться к Мартину Фаулеру и его книге "Patterns of Enterprise Application Architecture". В этой книге Фаулер подробно описывает принципы, паттерны и лучшие практики, связанные с использованием DTO и других шаблонов проектирования в приложениях предприятия.

НЛО прилетело и опубликовало эту надпись здесь

Вы правы, сериализация и десериализация - это не поведение объекта, а механизмы, которые позволяют преобразовывать объекты в форму, которую можно передавать по сети, и обратно.

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

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

Да, в общем случае это верно. Использование термина "amount" (сумма) подразумевает неопределенное количество или объем чего-то, в то время как "number" (количество) подразумевает определенное количество. В некоторых случаях использование терминов "amount" и "number" может быть неоднозначным, и зависит от контекста использования. Важно убедиться, что выбранный термин правильно отражает значение данных, которые вы хотите передать или обработать.

Вот пример DTO уже с 8.2:

final readonly class UserDTO
{
  public function __construct(
    public UuidInterface $id,
    public string $name,
    public Email $email, //Email - VO
    public DateTimeImmutable $createdAt,
  ) {}
}

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

  • DTO является неизменяемым объектом, что делает его безопасным и надежным для передачи между различными частями системы.

  • DTO содержит только те данные, которые необходимы для передачи, что уменьшает объем передаваемых данных и ускоряет процесс передачи.

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

Однако, можно внести несколько улучшений в этот пример:

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

  • DTO может содержать дополнительные поля, которые могут быть полезными для передачи дополнительной информации между компонентами системы. Например, можно добавить поле, которое указывает на то, какой именно тип пользователя (администратор, обычный пользователь и т.д.) передается.

  • Конструктор DTO можно дополнить параметром, который будет отвечать за установку флага "удаленного пользователя". Такой флаг может быть полезным для передачи информации о том, что данный пользователь был удален из системы.

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

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории