DTO (Data Transfer Object) - простий об'єкт для передачі даних між шарами застосунку: типізовані поля без бізнес-поведінки.
Проблема масивів:
public function handle(array $data): Order
{
$data['customer_id']; // а чи є такий ключ? який тип? як він називається - customerId?
}
Масив не має контракту: невідомо, які ключі обов'язкові, яких типів, - доводиться шукати, звідки він прийшов. Помилки в назвах ключів знаходяться лише під час виконання.
DTO на сучасному PHP:
final readonly class PlaceOrderData
{
/** @param list<OrderLineData> $lines */
public function __construct(
public int $customerId,
public array $lines,
public ?string $comment = null,
public DeliveryMethod $delivery = DeliveryMethod::Courier,
) {}
public static function fromRequest(StoreOrderRequest $request): self
{
return new self(
customerId: $request->user()->id,
lines: array_map(OrderLineData::fromArray(...), $request->validated('items')),
comment: $request->validated('comment'),
delivery: DeliveryMethod::from($request->validated('delivery')),
);
}
}
Що дає DTO:
- явний контракт: з сигнатури
handle(PlaceOrderData $data)видно, що потрібно операції; - типи й підказки IDE, перевірка статичним аналізом;
- незмінність (
readonly): дані не зміняться непомітно по дорозі; - незалежність від джерела: та сама дія викликається з HTTP (
fromRequest), команди Artisan, імпорту CSV, тестів; - енуми й об'єкти-значення замість рядків.
Де DTO корисні найбільше:
- вхід бізнес-операцій (action-класи, сервіси);
- відповіді сторонніх API - перетворити JSON на типізовані об'єкти одразу на межі, а не тягнути масиви по коду;
- межі модулів - модуль віддає DTO, а не свої моделі Eloquent.
Інструменти:
- readonly-класи PHP 8.2+ з властивостями конструктора - достатньо для більшості випадків;
spatie/laravel-data- DTO з вбудованою валідацією, перетворенням з/у масив, Request, моделі, генерацією TypeScript-типів;- Form Request + метод
toDto().
Чого уникати: DTO на кожну модель «про всяк випадок», які дублюють поля моделі один в один, - це шар без користі. DTO вводять там, де потрібен контракт між частинами коду.