DEV Community

AL EMRAN
AL EMRAN

Posted on • Edited on

Data Transfer Object (DTO) in Laravel

Data Transfer Object (DTO) in Laravel

Understanding DTOs in Modern PHP Applications: A Comprehensive Guide

Introduction

In modern PHP applications, particularly in systems like Doplac CRM, Data Transfer Objects (DTOs) play a crucial role in maintaining clean, maintainable, and type-safe code. This article explores the concept of DTOs, their benefits, and practical implementation strategies in PHP applications.

What is a DTO?

A Data Transfer Object (DTO) is a design pattern that encapsulates data for transfer between different layers of your application. Think of it as a specialized container that safely carries data across boundaries in your system, much like how a shipping container standardizes the transport of goods across different modes of transportation.

Why Should You Use DTOs?

1. Type Safety and Transparent Data Definition

DTOs provide a robust type system that catches errors at compile-time rather than runtime. By explicitly defining data types for each property, you create a contract that both developers and IDEs can understand and validate.

// Without DTO - Prone to errors
function processUser($data) {
    $name = $data['name'];  // What if 'name' doesn't exist?
    $email = $data['email']; // What if it's not a valid email?
}

// With DTO - Type-safe and clear
function processUser(UserDto $data) {
    $name = $data->name;    // IDE autocompletion works
    $email = $data->email;  // Property is guaranteed to exist
}
Enter fullscreen mode Exit fullscreen mode

2. Error Prevention and Code Clarity

DTOs act as a safeguard against common programming mistakes by providing:

  • Clear property definitions that IDEs can autocomplete
  • Type hints that prevent passing incorrect data types
  • Centralized validation rules
  • Explicit documentation of expected data structures

3. Clean and Maintainable Code

DTOs contribute to clean code principles by:

  • Separating data structure from business logic
  • Providing a single source of truth for data schemas
  • Making code intentions clear through explicit property definitions
  • Enabling easier code navigation and refactoring

4. Enhanced Security Through Type Hinting

Modern PHP's type system, when used with DTOs, provides an additional layer of security:

  • Runtime type checking prevents injection of unexpected data types
  • Validation can be enforced at the DTO level
  • Input sanitization can be centralized in the DTO

5. Performance Optimization

DTOs can improve application performance by:

  • Reducing database queries through selective data transfer
  • Minimizing network payload by including only necessary fields
  • Enabling easy caching of data objects

6. Improved Testability

DTOs make testing more straightforward and reliable:

  • Easy to mock for unit tests
  • Clear separation of concerns
  • Predictable data structures for assertions

Practical Implementation

Basic DTO Implementation

Here's a basic implementation that showcases type safety and validation:

use Illuminate\Http\Request;

class UserDto
{
    public function __construct(
        public readonly string $name,
        public readonly string $email,
        public readonly ?string $address,
        public readonly DateTime $createdAt = new DateTime()
    ) {}

    public static function fromRequest(Request $request): self
    {
        return new self(
            name: $request->input('name'),
            email: $request->input('email'),
            address: $request->input('address'),
        );
    }
}
Enter fullscreen mode Exit fullscreen mode

Advanced DTO with Validation

This implementation includes validation and error handling:

use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;

class UserDto
{
    public function __construct(
        public readonly string $name,
        public readonly string $email,
        public readonly ?string $address,
    ) {
        $this->validateEmail();
    }

    public static function fromRequest(Request $request): self
    {
        $validated = $request->validate([
            'name' => ['required', 'string', 'max:100'],
            'email' => ['required', 'email', 'max:200'],
            'address' => ['nullable', 'string', 'max:500'],
        ]);

        return new self(
            name: $validated['name'],
            email: $validated['email'],
            address: $validated['address'] ?? null,
        );
    }

    private function validateEmail(): void
    {
        if (!filter_var($this->email, FILTER_VALIDATE_EMAIL)) {
            throw new ValidationException('Invalid email format');
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Best Practices and Recommendations

  1. Keep DTOs Immutable: Use readonly properties in PHP 8.1+ to prevent accidental modifications.
  2. Avoid Complex Logic: DTOs should primarily handle data transfer and basic validation.
  3. Use Named Arguments: They make DTO instantiation more readable and less error-prone.
  4. Don't Rely on External Packages: Native PHP features are usually sufficient for implementing DTOs.
  5. Document Your DTOs: Use PHPDoc to describe properties and their purposes.

Practical Usage Example

// Controller
public function createUser(Request $request)
{
    $userDto = UserDto::fromRequest($request);

    // DTO guarantees type safety and data validity
    $user = $this->userService->createUser($userDto);

    return response()->json($user);
}

// Service
public function createUser(UserDto $dto): User
{
    // Safely access validated and typed properties
    return User::create([
        'name' => $dto->name,
        'email' => $dto->email,
        'address' => $dto->address,
    ]);
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

DTOs are more than just data containers – they're a powerful tool for maintaining code quality, ensuring type safety, and improving application architecture. While it might seem like extra work initially, the benefits in terms of code maintenance, error prevention, and developer experience make them invaluable in modern PHP applications.

Thank you for your time to read the article.

You can Follow me on Twitter

Our Products:

Doplac CRM

Top comments (7)

Collapse
 
alnahian2003 profile image
Al Nahian

Excellent writing, Emran vai 👐🏼
Bookmarked ✅

Collapse
 
emrancu profile image
AL EMRAN

You are most welcome.
Thank you for your inspiration.

InshaAllah will try to post regularly.
You can follow me on Twitter

Collapse
 
rafaacioly profile image
Rafael Acioly • Edited

Every time I see DTO I immediately think in Java old school code.

On my project, I like to use only the entity name in a specific namespace, e.g:

namespace App\Entities\PartnerName\Product;

class Product {}
Enter fullscreen mode Exit fullscreen mode

If there's a conflict, I use alias:

use App\Entities\PartnerName\Product as PartnerNameProduct;
Enter fullscreen mode Exit fullscreen mode
Collapse
 
emrancu profile image
AL EMRAN

May be but it is effective.
I do not like that. You should follow standard approach.

Collapse
 
rafaacioly profile image
Rafael Acioly

DTO is a not a standard is a convention, so you can use it or not, it doesn't matter.

Thread Thread
 
emrancu profile image
AL EMRAN

Yeah.

Collapse
 
verronknowles profile image
Verron Knowles

DTOs are fluff surrounding common functionality found in a language. Also a huge waste of time in terms of troubleshooting and development. Your examples are already ripe with abuse by handling data structure (models), http requests and validation when laravel already has places/homes for this stuff.

I'd argue DTOs aren't needed at all (even in Symfony) as why would you want your the context you get from the object instantiation lost. This introduces too much isolation and overhead as context should only be lost when objects leave your application. Objects are passed by reference in PHP, so more performant.