Les DTO sont des classes qui permettent de transférer des données à travers des systèmes. Ils ont pour but d’uniquement transmettre des données. Ajouter de la logique métier dedans les transforment en ValueObject.
Exemple de DTO d’un utilisateur:
readonly class Utilisateur {
public function __construct(
public string $nom,
public string $prenom,
public string $identifiant,
public \DateTimeImmutable $dateDeNaissance
) {}
}
Avantages
- Séparation des préoccupations : les DTO isolent la logique métier de la représentation des données, ce qui donne lieu à un code plus propre, plus maintenable et plus facile à comprendre.
- Validation des données : les DTO permettent la validation des données avant qu’elles ne soient traitées par d’autres couches d’application, garantissant ainsi que seules les données valides sont utilisées.
- Cohérence : en fournissant une structure cohérente pour le transfert de données, les DTO simplifient la gestion et le traitement des données provenant de diverses sources.
- Sécurité : les DTO peuvent protéger votre application contre toute manipulation non autorisée de données en contrôlant quelles données sont accessibles et modifiables.
- Tests : étant donné que les DTO sont des objets simples sans logique métier intégrée, ils sont plus simples à simuler et à tester.
- Transformation : les DTO facilitent la transformation des données dans les formats requis par différentes couches d’application.
- Immuabilité : les DTO favorisent souvent l’immuabilité, ce qui signifie qu’une fois créés, leur état ne peut pas changer. Cette fonctionnalité apporte plusieurs avantages :
- Prévisibilité : les objets immuables sont prévisibles et plus faciles à raisonner, car leur état reste constant après leur création.
- Thread-Safety : l’immuabilité prend intrinsèquement en charge la sécurité des threads, simplifiant ainsi le traitement simultané.
- Débogage : le débogage est plus facile avec des objets immuables car leur état est garanti de rester inchangé tout au long de leur cycle de vie.
PHP 8.2
Avec PHP 8.2, l’introduction des classes en lecture seule (readonly) améliore l’utilisation des DTO. Les classes en lecture seule éliminent le besoin de définir explicitement les propriétés comme étant en lecture seule, simplifiant ainsi vos implémentations de DTO.
- Code simplifié : les classes en lecture seule rendent automatiquement les propriétés immuables et améliorant la clarté de notre code.
- Sécurité renforcée : en garantissant que les propriétés ne peuvent pas être modifiées une fois définies, les classes en lecture seule améliorent l’intégrité et la sécurité des données.
- Facilité de maintenance améliorée : l’utilisation de classes en lecture seule conduit à un code plus propre et plus facile à maintenir, car l’immuabilité des données est imposée par le langage lui-même.
Utilisation
Il peut être intéressant de créer ses propres constructeur dans nos DTO en fonction des composants de notre application.
Exemple de DTO avec un constructeur à partir d’un model Eloquent:
readonly class Utilisateur {
public function __construct(
public string $nom,
public string $prenom,
public string $identifiant,
public \DateTimeImmutable $dateDeNaissance
) {}
public static function createFromModel(User $user): self
{
return new self(
$user->nom,
$user->prenom,
$user->identifiant,
$user->dateDeNaissance->toDateTimeImmutable(),
);
}
}
On peut aussi imaginer une moyen de sérialiser notre DTO afin de le préparer au transfert.
Exemple de DTO serialisable en tableau:
readonly class Utilisateur {
public function __construct(
public string $nom,
public string $prenom,
public string $identifiant,
public \DateTimeImmutable $dateDeNaissance
) {}
public function toArray(): array
{
return [
'nom' => $this->nom,
'prenom' => $this->prenom,
'identifiant' => $this->identifiant,
'dateDeNaissance' => $this->dateDeNaissance->format(),
];
}
}
Ici on créer une classe d’abstraction générique de DTO qui permet facilement de créer celui-ci peut importe la source et peut être sérialisé facilement.
/**
* @template TModel
*/
abstract class AbstractDTO
{
public function __construct(array $data)
{
$this->createFromArray($data);
}
/**
* @param TModel $model
*/
abstract public function createFromModel($model): self;
/**
* @return array<array-key, mixed>
*/
public function toArray(): array
{
$properties = get_object_vars($this);
return array_filter($properties, function ($property) {
return $property !== null;
});
}
public function toJson(): string
{
return json_encode($this->toArray());
}
/**
* @param array<array-key, mixed> $data
*/
protected function createFromArray(array $data): void
{
foreach ($data as $key => $value) {
if (property_exists($this, $key)) {
$this->$key = $value;
}
}
}
}