Using DTOs in Laravel for Cleaner, More Maintainable Code

- James Harley

Data Transfer Objects (DTOs) are objects used for carrying data between different parts (or layers) of an application. In a Laravel application, DTOs help keep data well-structured and consistent by explicitly defining which data is shared between classes and passed in responses. By embracing DTOs, you gain several benefits such as:

Decoupling

By using DTOs, you can decouple the data being transferred between different layers of your application. This can help to make your code more flexible and easier to maintain. For example, in a Laravel application when querying a model directly you may end up passing data in a response that is unneeded by the client. Using a DTO, you can define exactly what data is being transferred between the controller and the view. For example, if you are using a User model which contains more data than you need for your response, by using a DTO you can define exactly what data is going to be passed in the response.

// Not using a DTO

class UserController extends Controller
{
  public function show()
  {
    $user = User::first();

    // Passes all the data from the user model (baring excluded properties like `password`) into the response
    return response()->json($user);
  }
}
// Using a DTO

// DTO class
class UserData
{
  public function __construct(
    public readonly string $name,
    public readonly string $email
  ) {}

  // Only get the users name and email and nothing else
  public static function from(User $user): self
  {
    return new self(name: $user->name, email: $user->email);
  }
}

// Controller
class UserController extends Controller
{
  public function show()
  {
    $userData = UserData::from(User::first());

    // We only pass the users name and email in the response
    return response()->json($userData);
  }
}

We are clearly defining the data that will be included in the response, effectively preventing any unwanted data from reaching the client, much like a resource response. Furthermore, we are not only restricting the data returned in the response but also utilising Data Transfer Objects (DTOs) to pass data to a method. For example:

class CarData
{
  public function __construct(
    public readonly string $make,
    public readonly string $model
  ) {}

  public static function from(Car $car): self
  {
    return new self(make: $car->make, model: $car->model);
  }

}

class Car
{
  public function describe(CarData $carData): string
  {
    return "This car is a {$carData->make} {$carData->model}";
  }
}

$carModel = Car::create([
  'make' => 'Ford',
  'model' => 'Focus'
]);

// Now we can create a DTO and pass it to the `describe` method
$car = CarData::from($carModel);

new Car()->describe($car); // This car is a Ford Focus

We now know the properties that are being passed to the describe method and can be sure that the data is correct.

IDE Support

Using a DTO can also help with IDE support. By defining the structure of the data being transferred between different layers of your application, you can take advantage of your IDE’s autocomplete and type hinting features. This can help to reduce the chances of errors in your code and make it easier to work with.

Implementing DTOs in Your Laravel Application

It’s super simple to start using DTOs within your Laravel application. As you can see from the previous examples, all you need is a class and a way to structure the data you want from the parameters passed to the class, for example, the from method in the UserData class. If you ever want to extend the functionality of your DTOs you can use Spaties Laravel Data package. Installing the Laravel Data package is what I would recommend unless you only want to use the basic features of a DTO. The Laravel Data package allows you to extend from the Data class and make use of DTOs for requests or lazy properties. The package comes with a vast amount of features to make your DTOs more powerful and easier to use. It also comes packed with a command to make it slightly faster to create new DTOs.