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
{
// ...
}
}
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
) {}
}
Now, we can refactor the execute() method in the CreateProductclass:
public function execute(ProductDto $data): Product
{
// ...
}
IDE Benefits
By using a DTO class instead of an associative array, your IDE provides better support by identifying the required attributes:
And also in the use of this 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']
);
}
Usage:
$data = ProductDto::fromRequest($request);
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
{
// ...
}
}


Top comments (0)