DEV Community

Cover image for DTOs and PHP: Simplifying Data Transfer Between Application Layers
Marcelo Chiaretto
Marcelo Chiaretto

Posted on

DTOs and PHP: Simplifying Data Transfer Between Application Layers

The DTO Pattern

The DTO (Data Transfer Object) is a design pattern aimed at creating objects used exclusively for transferring data between different layers of an application. It is typically an "anemic" object, meaning the class contains only attributes and lacks methods for data manipulation — focusing solely on object construction.

Why not use an array to receive data in a method?

Consider the following CreateProduct class:

<?php 

namespace App\Actions\Product;

use App\Product\Contracts\ProductRepository;

class CreateProduct 
{
    public function __construct(private ProductRepository $repository) {}

    public function execute(array $data): Product 
    {
        // ...
    }
}
Enter fullscreen mode Exit fullscreen mode

Since the execute() method receives an associative array, anyone instantiating this class would need to look inside the method's implementation to understand which keys are required in the array.

Replacing the array with a DTO

In PHP, we can build a DTO class using the readonly modifier (introduced in PHP 8.1) to ensure that the object's attributes remain immutable.

<?php

namespace App\Dto;

readonly class ProductDto 
{
    public function __construct(
        public string $name,
        public string $description,
        public float $price,
        public int $quantity,
        public int $categoryId,
        public int $brandId,
        public string $sku,
        public string $ean
    ) {}
}
Enter fullscreen mode Exit fullscreen mode

Now, we can refactor the execute() method in the CreateProductclass:

public function execute(ProductDto $data): Product 
{
    // ...
}
Enter fullscreen mode Exit fullscreen mode

IDE Benefits

By using a DTO class instead of an associative array, your IDE provides better support by identifying the required attributes:

Instantiating the ProductDto class

And also in the use of this data:

Using the data

Going further with static methods

To simplify the creation of these objects, we can implement static factory methods:

public static function fromRequest(Request $request): self 
{
    return new self(
        name: $request->name,
        description: $request->description,
        price: $request->price,
        quantity: $request->quantity,
        categoryId: $request->category_id,
        brandId: $request->brand_id,
        sku: $request->sku,
        ean: $request->ean
    );
}

public static function fromArray(array $data): self 
{
    return new self(
        name: $data['name'],
        description: $data['description'],
        price: $data['price'],
        quantity: $data['quantity'],
        categoryId: $data['categoryId'],
        brandId: $data['brandId'],
        sku: $data['sku'],
        ean: $data['ean']
    );
}
Enter fullscreen mode Exit fullscreen mode

Usage:

$data = ProductDto::fromRequest($request);
Enter fullscreen mode Exit fullscreen mode

When not to use DTOs

I don't see the need to use a DTO as a parameter in public methods that already clearly indicate the expected data. For example, a method that fetches a list of products by their IDs:

class FindProducts 
{
    public function execute(array $productsIds): Product 
    {
        // ...
    }
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)