php 61 lines · 6 steps

Typed request DTOs in Laravel

A readonly data object validates and normalizes an HTTP request into an immutable, typed shape.

Explained by highlit
1<?php
2 
3namespace App\Http\Requests\DataObjects;
4 
5use App\Enums\Currency;
6use Illuminate\Contracts\Support\Arrayable;
7use Illuminate\Http\Request;
8 
9final readonly class CreateInvoiceData implements Arrayable
10{
11 public function __construct(
12 public string $customerId,
13 public Currency $currency,
14 public int $amountInCents,
15 public ?string $reference,
16 public array $lineItems,
17 public ?string $notes,
18 ) {
19 }
20 
21 public static function fromRequest(Request $request): self
22 {
23 $validated = $request->validate([
24 'customer_id' => ['required', 'uuid'],
25 'currency' => ['required', 'string', 'size:3'],
26 'amount' => ['required', 'numeric', 'min:0.01'],
27 'reference' => ['nullable', 'string', 'max:64'],
28 'line_items' => ['required', 'array', 'min:1'],
29 'line_items.*.description' => ['required', 'string'],
30 'line_items.*.quantity' => ['required', 'integer', 'min:1'],
31 'notes' => ['nullable', 'string'],
32 ]);
33 
34 return new self(
35 customerId: $validated['customer_id'],
36 currency: Currency::from(strtoupper($validated['currency'])),
37 amountInCents: (int) round($validated['amount'] * 100),
38 reference: $validated['reference'] ?? null,
39 lineItems: array_map(
40 static fn (array $item): array => [
41 'description' => trim($item['description']),
42 'quantity' => (int) $item['quantity'],
43 ],
44 $validated['line_items'],
45 ),
46 notes: $validated['notes'] ?? null,
47 );
48 }
49 
50 public function toArray(): array
51 {
52 return [
53 'customer_id' => $this->customerId,
54 'currency' => $this->currency->value,
55 'amount_in_cents' => $this->amountInCents,
56 'reference' => $this->reference,
57 'line_items' => $this->lineItems,
58 'notes' => $this->notes,
59 ];
60 }
61}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1A dedicated DTO turns loose request arrays into a typed, immutable contract your app can trust.
  2. 2Centralizing validation and normalization in one factory keeps controllers thin and consistent.
  3. 3Implementing Arrayable gives a clean, explicit mapping back to array form for storage or responses.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Typed request DTOs in Laravel — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code