DTO (Data Transfer Object) — це простий об’єкт, який переносить дані між шарами системи в передбачуваному вигляді. Value Object — це “значення з правилами”: об’єкт, який гарантує, що всередині завжди валідний формат (наприклад, гроші, дата-період, slug, email). На легкому рівні тобі не треба будувати складну доменну модель. Тобі треба навчитися одному: перед тим як писати в базу, дані мають бути нормалізовані і зібрані в чітку структуру, а не “як прийшло з форми”.
Чому “сирий масив” — проблема. У Laravel легко зробити $data = $request->validated() і віддати його в сервіс. Але навіть валідовані дані можуть бути в “поганому” форматі: зайві пробіли, різний регістр, різні формати дат, числа як рядки, чекбокси як 'on', порожні строки там, де ти хочеш null. Якщо ти це не нормалізуєш, база заповнюється сміттям: потім фільтри по даті працюють дивно, slug має пробіли, boolean порівнюється як строка, а пошук стає непередбачуваним.
Легкий DTO-рівень — це коли ти робиш клас типу TagData, SectionData, ResourceData, NewsData і маєш один метод “зроби з масиву” (або з FormRequest). У цьому класі ти приводиш дані до нормальної форми і повертаєш об’єкт з типізованими властивостями. Сервіс отримує DTO, а не масив. У результаті сервісу не потрібно щоразу пам’ятати “trim тут, lowercase там”, бо DTO гарантує формат.
Нормалізація — це конкретні технічні кроки, які треба робити завжди. Рядки треба обрізати (trim) і часто чистити від “невидимих” символів. Порожні рядки часто варто перетворити на null, щоб в базі не було “порожнього тексту” замість відсутності значення. Slug зазвичай треба привести до нижнього регістру і замінити пробіли/підкреслення на дефіси. Email — у lower-case. Дати — привести в один формат і одну часову зону. Boolean — привести до true/false, а не залишати '0'/'1'/'on'. Числа і гроші — привести до чіткого типу, часто до integer у “копійках”, щоб уникнути проблем з float.
DTO добре працює разом з FormRequest. FormRequest гарантує, що поля існують і відповідають базовим правилам. DTO робить наступний рівень: приводить до єдиного формату, прибирає “сміття” і робить типи передбачуваними. Після цього сервіс працює як механізм сценарію, а не як набір “перевірок на кожному кроці”.
Value Objects — це наступний крок після DTO, коли ти хочеш зафіксувати правила для одного значення. Наприклад, Slug, Money, DateRange, Email, Percent. На легкому рівні Value Object може бути дуже простим: він приймає рядок/число і або створюється, або кидає виняток, якщо формат неправильний. Перевага в тому, що ти більше не передаєш по коду “просто рядок”, ти передаєш “Slug”, який завжди нормальний. І тоді не треба кожного разу згадувати правила.
Найкорисніші Value Object для адмінок і CMS — це Slug і DateRange. Slug — бо він часто використовується в URL, має бути чистим і унікальним. DateRange — бо фільтри “від/до” постійно ламаються через межі дня і часові зони. Money — якщо у вас рахунки та суми, бо float майже гарантовано дасть помилки на масштабі. Навіть якщо зараз “якось працює”, в майбутньому ці дрібні неточності перетворюються на фінансові баги.
Як це виглядає в реальному потоці create/update без зайвого ускладнення. Контролер приймає FormRequest і отримує валідовані дані. Далі замість того, щоб передати масив у сервіс, ти створюєш DTO: TagData::fromArray($request->validated()). DTO нормалізує. Сервіс отримує DTO і записує в базу тільки те, що треба. Якщо є 2+ таблиці або pivot — робиться транзакція. У такій схемі вся система стає менш крихкою: змінив правило нормалізації — змінив в одному місці.
Типові помилки при DTO/VO. Перша — робити DTO, але знову пхати туди “все підряд”, включно зі службовими полями, які не мають приходити з UI. DTO має бути whitelist-представленням даних, а не копією request. Друга — робити Value Objects занадто складними на старті і “задушити” навчання. Легкий рівень — це 1–2 VO максимум (Slug, Money), і тільки якщо реально є сенс. Третя — робити нормалізацію в 5 місцях: частково в контролері, частково в сервісі, частково в моделі. Потрібна одна точка правди: або DTO, або mutator, але узгоджено.
Висновок: DTO — це спосіб тримати дані в системі в однаковому, передбачуваному вигляді, а Value Object — спосіб зафіксувати правила для конкретних значень. На легкому рівні це означає одне: перед записом у БД ти не зберігаєш “як прийшло”, ти зберігаєш нормалізовані, типізовані дані. Це зменшує кількість багів, робить фільтри та пошук стабільнішими, і дає основу для масштабування, коли проєкт стане більшим і “дрібні” неточності почнуть боліти.