В рамках последнего семинара мы обсуждали концепцию DTO (Data Transfer Object). Главная особенность DTO заключается в том, что они содержат значения исключительно примитивных типов (строки, целые числа, логические значения), списки или ассоциативные массивы с такими значениями, включая и «вложенные» DTO. Я не могу точно сказать, кто придумал эту идею, но я использую ее, потому что она делает DTO структурами данных, которые энфорсят только схему заключенных в них значений (имена полей, ожидаемые типы, обязательные и необязательные поля), оставляя их семантику в покое. Это позволяет нам создавать DTO из любого источника данных, например из значений, полученных из формы ввода данных, аргументов командной строки, JSON, XML, Yaml и т. д.

Использование примитивных значений в DTO является наглядной демонстрацией того, что эти значения не валидируются. DTO просто используется для передачи или переноса данных с одного слоя в другой. И вот в этом контексте во время семинара у нас возник вопрос: можем ли мы считать DateTimeImmutable значением примитивного типа? Если да, то можем ли мы использовать этот тип внутри DTO?

Мне кажется, что это достаточно интересный вопрос для разбора. Хочется сразу ответить «нет», но почему?

Как нам понять, удовлетворяет ли что-либо наш предикат? Для начала мы должны определить сам предикат. Когда мы оперируем абстрактными формулировками, то этот первый шаг вполне очевиден, но при обсуждении конкретных вопросов часто неясно, что разговор должен начинаться с определений; нам так не терпится сразу же перейти к ответу! Итак, чтобы ответить на это вопрос, нам для начала нужно определить, что является значением примитивного типа.

Во-первых, мы могли бы считать «примитивным» то, что «уже нельзя разделить составные на части». В этом смысле, если тип Money состоит из целого числа, обозначающего количество центов, и строки, объявляющей валюту, Money не является примитивным типом, а целое число и строка, которые заключены в этот тип, являются, потому что нет никакого смысла делать их составными. Хотя можно сказать, что символ представляет из себя нечто более примитивное, чем строка, в PHP они представлены одним типом string. DateTimeImmutable в этом смысле не является примитивным, в отличие от содержащегося в нем значения (таймстемп).

Во-вторых, мы могли бы считать, что «примитивным» является то, что «не является объектом», или, как PHP называет такие значения, скаляр. Опять же, характеристика «примитивный» должна учитывать то, что сам язык программирования считает примитивным, потому что, например, в Java есть строки, которые считаются примитивными значениями, но, тем не менее, являются объектами. Но в любом случае у Java свои странные отношения с примитивными значениями, потому что строки, целые числа и т. д. выглядят в Java-коде очень примитивно (т.е. нам не нужно писать new String ("a string") для объявления, достаточно просто написать "a string"). В PHP эта концепция намного очевидней.

Если смотреть на «примитивность» с этой точки зрения, значение DateTimeImmutable не может считаться значением примитивного типа, потому что это объект. Но в Java это ему не помешало бы, потому что другие примитивные значения считаются примитивными независимо от того, являются ли они объектами.

В-третьих, мы могли бы считать «примитивными» «любые типы, которые язык предлагает нам по умолчанию». Такие типы часто называют «нативными». К сожалению, это не очень полезное определение, поскольку не до конца ясно, что является нативным. Существует ли «ядро» языка, которое определяет эти типы? В таком случае, откуда мы получаем DateTimeImmutable? Разве не в рамках расширения? Кроме того, можем ли мы считать файловые дескрипторы (ресурсы) примитивными типами? В конце концов, большая часть того, что является частью «ядра» или «родного» языка, совершенно произвольна.

В-четвертых, мы могли бы сформулировать «примитивность» в форме вопроса — «какие типы нам нужны для описания данных?» В этом смысле мы можем оглянуться на историю самого человечества и принять за примитивный тип числа (например, для описания ценности чего-либо), а также строки (например, для записи имени). Поэтому скорее всего дата и время не являются примитивными, потому что они состоят из строк (или символов) и чисел.

В-пятых, мы могли бы посчитать, что «примитивными» могут быть только «голые значения, не обязательно адекватные или правильные». Таким образом, “2” — это примитивное значение, оно не несет в себе информации, количество чего она представляет, поэтому мы не можем судить, правильное ли это значение. “UKj” — это примитивное значение, оно не говорит нам, что оно описывает, поэтому нет возможности судить об этом значении. Опираясь на это определение, DateTimeImmutable, безусловно, не является примитивным типом, потому что при создании его инстанса обрабатывается предоставленный аргумент строкового конструктора и выдает ошибку, если он не является адекватным. Или, что еще хуже, преобразует его в значение, которое имеет смысл, но может больше не соответствовать намерениям субъекта, создавшего значение.

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

Если я упустил какое-либо возможное определение «примитивности», напишите, пожалуйста, в комментариях.


А всех желающих приглашаем на открытый вебинар «Функциональное программирование на PHP», на котором мы:

  • поговорим про особенности ФП;

  • разберёмся со свёрткой списков (reduce);

  • напишем полезную монаду для валидации создаваемых сущностей (writer).

Регистрация открыта по ссылке для всех желающих.

Комментарии (3)



  1. abyrvalg
    15.10.2022 08:30

    DTO - это набор данных, которые можно однозначно сериализовать (ок, маршализировать) на одном конце и десериализовать на другом. Никаких требований к "примитивности" нет.

    Дата, время, деньги - сериализуются однозначно. Использоаать в DTO можно.


  1. rsmike
    15.10.2022 22:25

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

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