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
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1A dedicated DTO turns loose request arrays into a typed, immutable contract your app can trust.
- 2Centralizing validation and normalization in one factory keeps controllers thin and consistent.
- 3Implementing Arrayable gives a clean, explicit mapping back to array form for storage or responses.
Related explainers
php
<?php class NameParser {
Parsing a full name into components in PHP
string-parsing
arrays
normalization
Intermediate
8 steps
php
<?php namespace App\Services\Checkout;
Validating coupons with Laravel's Pipeline
pipeline
chain of responsibility
transactions
Intermediate
7 steps
php
<?php namespace App\Services;
How a password strength validator works in PHP
validation
regular-expressions
data-driven
Intermediate
8 steps
php
<?php namespace App\Services;
Building a cached daily leaderboard in Laravel
caching
aggregation
eager-loading
Intermediate
9 steps
rust
use chrono::{Duration, NaiveDate}; #[derive(Debug)] pub struct DateRange {
Parsing and iterating date ranges in Rust
error-handling
iterators
parsing
Intermediate
7 steps
go
package logging import ( "context"
Deduplicating log attributes in Go's slog
decorator-pattern
structured-logging
immutability
Intermediate
8 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/typed-request-dtos-in-laravel-explained-php-4941/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.