Using Data Transfer Objects (DTOs) in Laravel

26th January 2025
When building modern Laravel applications, ensuring clean, maintainable, and readable code is essential. Data Transfer Objects (DTOs) are a powerful tool to achieve this.
In this blog post, we'll explore what DTOs are, the problems they solve, and how to use them effectively in a Laravel application. By the end, you'll have a solid understanding of how to implement DTOs in your own projects.
What Are Data Transfer Objects?
A Data Transfer Object (DTO) is a simple object designed to carry data between parts of an application. DTOs often hold no business logic; their purpose is to encapsulate related data and pass it around in a structured way.
In our case, think of data transfer objects as nothing more than a PHP object that holds data to be passed around parts of the application.
What Problem Do DTOs Solve?
Encapsulation: Instead of passing raw arrays or multiple variables, DTOs group related data into a single, self-contained object.
Validation and Type Safety: DTOs enforce specific data structures, ensuring you work with strongly typed values, reducing runtime errors.
Readability: By naming DTO properties, you make your code self-documenting, improving its clarity.
Testability: DTOs make unit tests easier because you can pass them directly without mocking complex HTTP requests or raw input arrays.
Consider a controller method handling a user update request:
<?php
// Controller.php
public function update(Request $request)
{
$data = $request->only([
'name',
'email'
]);
$user = User::findOrFail($request->id);
$user->update($data);
return response()->json([
'message' => 'User updated'
]);
}
This approach works but has drawbacks:
Potential for typos or missing keys in $request->only.
Weakly typed and less readable.
Any time you are passing around large chunks of related data inside an array, ask yourself if this could be a data transfer object?
Implementing DTOs in Laravel
In the above example we could create a data transfer object with the following code.
<?php
namespace App\DataTransferObjects;
class User
{
public readonly string $name;
public readonly string $email;
private function __construct(
string $name,
string $email
)
{
$this->name = $name;
$this->email = $email;
}
public static function fromRequest(
Request $request
): self
{
return new self(
$request->get('name'),
$request->get('email')
);
}
}
In this example, the first thing you might notice is that we are making the properties public and readonly, DTOs are very simple in nature and are only for passing around data in a structured manner, so public properties are perfectly fine.
We want the data structure to be immutable and unable to change. If we do require different values then we can just simply create a new DTO instead.
Benefits of Using fromRequest() and Private Constructors
Using a private constructor and a static factory method like fromRequest() brings several benefits.
A private constructor ensures that the DTO instance cannot be initialised in an inconsistent way, the only way to create the DTO is through the static method on the class.
With this in place we could add extra validation inside the fromRequest()
method to make sure each value in the DTO is valid.
In our example, we can create a DTO from the request.
<?php
use App\DataTransferObjects\User as UserDTO;
$userDto = UserDTO::fromRequest($request);
Using the DTO in our Controller
We can now use the data transfer object within our controller in the following way.
<?php
namespace App\Http\Controllers;
use App\DataTransferObjects\User as UserDTO;
use Illuminate\Http\Request;
class UserController extends Controller
{
public function update(Request $request)
{
$dto = UserDTO::fromRequest($request);
$userModel = User::findOrFail($request->id);
$userModel->update([
'name' => $dto->name,
'email' => $dto->email,
]);
return response()->json([
'message' => 'User updated'
]);
}
}
Notice how well this reads, we create a data transfer object from the given request, find the user with the id in the request and update the user with the values from the data transfer object and return a JSON response.
Testing Data Transfer Objects
Because we are dealing with simple PHP objects and classes, testing is very simple.
<?php
use App\DataTransferObjects\User as UserDTO;
it('creates a DTO from request data', function () {
$request = new Request([
'name' => 'Jane Doe',
'email' => '[email protected]'
]);
$dto = UserDTO::fromRequest($request);
expect($dto->name)->toBe('Jane Doe');
expect($dto->email)->toBe('[email protected]');
});
Conclusion
DTOs are a simple yet powerful tool for structuring data in your Laravel applications. By encapsulating related data, enforcing type safety, and improving code readability, DTOs make your code more robust and maintainable.
Using patterns like fromRequest() and private constructors ensures proper usage while simplifying testing.
Give DTOs a try in your next Laravel project, you’ll appreciate the clarity and organisation they bring.