Introduction
In modern Laravel applications, keeping code organized, reusable, and easy to maintain is essential. One way to achieve this is by using a DTO (Data Transfer Object). DTOs act as structured containers that carry data between layers (like requests, services, or models) — without business logic or side effects.
Think of a DTO as a clean “data package” that ensures consistency and separation of concerns when handling data from different sources such as forms, APIs, or database models.
Why DTOs Are Important
Without DTOs, controllers or services often get messy — juggling request validation, business logic, and data transformations all at once. Using a DTO brings:
Clean separation of concerns — keeps controllers lightweight.
Consistent data structure — ensures predictable input/output formats.
Easy testing — you can unit test data transformations separately.
Improved maintainability — reduces duplication and tight coupling.
⚠️ Note: DTOs don’t replace Eloquent models or form requests — they complement them by providing a layer between raw data and model persistence.
When to Use DTOs
You should consider using a DTO whenever:
You’re passing complex or repeated data between layers (like controller → service → repository).
You want to keep controller methods simple and clean.
You’re working with APIs or request data that need formatting before saving.
You want to validate and normalize data before passing it to models.
Simple Example of a DTO in Laravel
Here’s a practical example from a vehicle rental system, showing a clean DTO implementation for VehicleRentalHistory.
Step 1: Create a DTO Class
<?php
namespace App\DTOs;
use Illuminate\Http\Request;
use Carbon\Carbon;
class VehicleRentalHistoryDTO
{
public int $vehicle_id;
public ?int $user_id;
public string $rental_start;
public ?string $rental_end;
public ?float $total_amount;
public float $discount_applied;
public ?float $final_amount;
public string $status;
public ?string $notes;
public function __construct(array $data)
{
$this->vehicle_id = $data['vehicle_id'];
$this->user_id = $data['user_id'] ?? null;
$this->rental_start = $data['rental_start'];
$this->rental_end = $data['rental_end'] ?? null;
$this->total_amount = isset($data['total_amount']) ? (float) $data['total_amount'] : null;
$this->discount_applied = $data['discount_applied'] ?? 0;
$this->final_amount = isset($data['final_amount']) ? (float) $data['final_amount'] : null;
$this->status = $data['status'] ?? 'reserved';
$this->notes = $data['notes'] ?? null;
}
public static function fromArray(array $data): self
{
return new self($data);
}
public static function fromRequest(Request $request): self
{
return new self($request->validated() ?? $request->all());
}
public function toArray(): array
{
return [
'vehicle_id' => $this->vehicle_id,
'user_id' => $this->user_id,
'rental_start' => $this->rental_start,
'rental_end' => $this->rental_end,
'total_amount' => $this->total_amount,
'discount_applied' => $this->discount_applied,
'final_amount' => $this->final_amount,
'status' => $this->status,
'notes' => $this->notes,
];
}
}
This DTO handles clean data mapping between incoming requests and database models. Notice how it also includes helper static methods like fromRequest() and toArray() for convenience.
Step 2: Use DTO in Your Controller
In your controller, instead of working directly with request data, you can now use the DTO:
<?php
namespace App\Http\Controllers\API;
use App\Http\Controllers\Controller;
use App\Models\VehicleRentalHistory;
use App\DTOs\VehicleRentalHistoryDTO;
use App\Http\Requests\StoreVehicleRentalHistoryRequest;
use Illuminate\Http\JsonResponse;
class VehicleRentalHistoryController extends Controller
{
public function store(StoreVehicleRentalHistoryRequest $request): JsonResponse
{
$dto = VehicleRentalHistoryDTO::fromRequest($request);
$record = VehicleRentalHistory::create($dto->toArray());
return response()->json([
'message' => 'Rental history record created successfully.',
'data' => $record,
], 201);
}
}
The controller now looks cleaner and only focuses on the workflow — the DTO takes care of structuring data for the model.
Benefits of Using DTOs in Laravel
✅ Keeps controllers and models simple
✅ Centralizes data transformation logic
✅ Promotes reusable and testable code
✅ Makes future refactors or API changes easier
⚠️ Tip: DTOs shine when used with service layers, repository patterns, or API endpoints that require complex input transformations.
Conclusion
DTOs are a small but powerful addition to your Laravel architecture. They bridge the gap between raw input and business logic, promoting cleaner and more maintainable code. If you want your Laravel apps to scale smoothly and stay organized, start introducing DTOs into your workflow today.