DEV Community

shadowtje1990
shadowtje1990

Posted on • Originally published at Medium

How to Use Data Transfer Objects (DTOs) for Clean PHP Code

In modern web applications, it is common to have different layers of an application, such as the database layer, the business logic layer, and the presentation layer. Each layer is responsible for a specific task and communicates with the other layers through interfaces. However, when transferring data between these layers, it can be difficult to manage the data and ensure that it is transferred correctly. That’s where Data Transfer Objects (DTOs) come in.

A DTO is a design pattern used to transfer data between layers of an application. A DTO is an object that carries data between processes, such as between a web application and a database. It is a simple container for data that does not have any business logic, validation, or other operations.

One of the most common uses of DTOs is to transfer data between layers of an application. For example, when retrieving data from a database, the data may be returned as an array or a database result set. However, the business logic layer may require the data to be in a specific format or type. A DTO can be used to convert the data from one format to another and ensure that it is passed correctly to the next layer.

Another use of DTOs is to define the structure of the response objects returned by an API. When designing an API, it is important to define the structure of the response objects to ensure that they are consistent and easy to consume by client applications. A DTO can be used to define the structure of the response object and ensure that it contains all the necessary data.

Now that you have a good understanding of what Data Transfer Objects (DTOs) are and why they are useful in PHP applications, it’s time to dive into some practical examples. While the theory behind DTOs is important to understand, it can be a bit dry without real-world context.

1. UserDTO created through static method

class UserDTO {
        private int $id;
        private string $name;
        private string $email;

        private function __construct(int $id, string $name, string $email) {
            $this->id = $id;
            $this->name = $name;
            $this->email = $email;
        }

        public static function create(int $id, string $name, string $email): UserDTO {
            return new self($id, $name, $email);
        }

        public function id(): int {
            return $this->id;
        }

        public function name(): string {
            return $this->name;
        }

        public function email(): string {
            return $this->email;
        }
    }
Enter fullscreen mode Exit fullscreen mode

In this example, the UserDTO class has three private properties: $id, $name, and $email. The constructor is made private to ensure that the class can only be instantiated using the static create method.

The create method accepts the three properties as arguments and returns a new UserDTO object with those properties. The id, name, and email methods provide read-only access to the private properties of the class.

Using a private constructor and a static method to create the class helps to ensure that the data within the DTO is valid and can be used as intended, while preventing any external manipulation of the data.

Using private properties and getter methods, is more traditional and can be used in earlier versions of PHP. It allows you to define the properties separately from the constructor, which can make the code more modular and easier to test. It also provides better encapsulation, since the properties are private and can only be accessed through the getter methods. However, this approach requires more code to implement, and may be less concise and readable than the public readonly approach.

2. UserDTO using public readonly

class UserDTO {
        private function __construct(
            public readonly int $id,
            public readonly string $name,
            public readonly string $email
        ) {}

        public static function create(int $id, string $name, string $email): UserDTO {
            return new self($id, $name, $email);
        }
    }
Enter fullscreen mode Exit fullscreen mode

This approach, using public readonly properties, is only available in PHP 8.0 and later. It allows you to define the properties directly within the constructor signature, which can make the code more concise and easier to read. It also provides immutability out-of-the-box, since the properties are both public and readonly. However, this approach does not provide any methods for accessing the property values, which can be a disadvantage in some situations.

In summary, both approaches have their advantages and disadvantages, and the choice between them depends on the specific needs of your application. If you are using PHP 8.0 or later and require immutability, the public readonly approach may be a good choice. Otherwise, the private properties and getter methods approach provides better encapsulation and can be used in earlier versions of PHP.

As a bonus, if you are interested in how this works using traits. Please checkout my other article.
Creating a DTO with Traits in PHP

Top comments (0)