Digital solutions
Design Patterns avec Laravel

Value Object Pattern

  • Type : Créationnel
  • Difficulté : 3/10
  • Définition succincte : Le Value Object Pattern est un design pattern de création qui consiste à utiliser des objets immuables pour représenter une valeur. Un Value Object est un objet dont l'égalité ne dépend pas de l'identité mais de l'état de ses propriétés. Ces objets sont utiles pour encapsuler des valeurs simples, comme des coordonnées géographiques, des adresses, des montants d'argent, ou d'autres concepts qui sont mieux représentés par un objet que par des types primitifs.

Objectif du Value Object Pattern

L'objectif principal du Value Object Pattern est de modéliser des concepts du domaine qui se définissent uniquement par leurs valeurs, et non par une identité spécifique. Ces objets sont souvent immuables, ce qui signifie que leurs valeurs ne peuvent pas être modifiées après leur création. Cela permet d'éviter les effets de bord indésirables et de rendre le code plus prévisible.

Utilisation du Value Object Pattern dans Laravel et PHP

Les Value Objects sont particulièrement utiles pour encapsuler des concepts métier comme les adresses, dates, monnaies, ou autres unités de mesure qui peuvent être modélisées avec des valeurs spécifiques.

Exemple : Implémentation d'un Value Object pour la monnaie

Imaginons que tu développes un système de facturation dans Laravel et que tu aies besoin de manipuler des montants d'argent de manière sécurisée. Au lieu de représenter l'argent comme une simple variable de type entier ou flottant, tu peux créer un Value Object appelé Money pour encapsuler à la fois la valeur et la devise.

1. Créer le Value Object Money

// app/ValueObjects/Money.php

namespace App\ValueObjects;

use InvalidArgumentException;

class Money
{
    private int $amount;
    private string $currency;

    public function __construct(int $amount, string $currency)
    {
        // Valider la devise (par exemple, trois lettres pour représenter USD, EUR, etc.)
        if (!in_array($currency, ['USD', 'EUR', 'GBP'])) {
            throw new InvalidArgumentException("Invalid currency: {$currency}");
        }

        $this->amount = $amount;
        $this->currency = $currency;
    }

    // Récupérer la valeur du montant
    public function getAmount(): int
    {
        return $this->amount;
    }

    // Récupérer la devise
    public function getCurrency(): string
    {
        return $this->currency;
    }

    // Comparer deux objets Money
    public function equals(Money $other): bool
    {
        return $this->amount === $other->getAmount() && $this->currency === $other->getCurrency();
    }

    // Ajouter deux objets Money (de même devise)
    public function add(Money $other): Money
    {
        if ($this->currency !== $other->getCurrency()) {
            throw new InvalidArgumentException('Cannot add amounts with different currencies.');
        }

        return new Money($this->amount + $other->getAmount(), $this->currency);
    }
}

2. Utilisation du Value Object dans une commande CLI

Nous allons maintenant utiliser notre Value Object Money dans une commande CLI pour simuler des opérations de paiement et de facturation en additionnant deux montants d'argent.

// app/Console/Commands/ProcessPayment.php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use App\ValueObjects\Money;
use InvalidArgumentException;

class ProcessPayment extends Command
{
    protected $signature = 'payment:process {amount1} {currency1} {amount2} {currency2}';
    protected $description = 'Process a payment by adding two amounts of money';

    public function handle()
    {
        $amount1 = (int) $this->argument('amount1');
        $currency1 = $this->argument('currency1');
        $amount2 = (int) $this->argument('amount2');
        $currency2 = $this->argument('currency2');

        try {
            // Création des deux objets Money
            $payment1 = new Money($amount1, $currency1);
            $payment2 = new Money($amount2, $currency2);

            // Additionner les deux montants
            $totalPayment = $payment1->add($payment2);

            // Afficher le résultat dans la console
            $this->info("Total amount: {$totalPayment->getAmount()} {$totalPayment->getCurrency()}");

        } catch (InvalidArgumentException $e) {
            $this->error($e->getMessage());
        }
    }
}

3. Exécution de la commande

Voici comment tu peux exécuter cette commande pour tester l'addition de montants d'argent :

php artisan payment:process 1000 USD 500 USD

Cela renverra :

Total amount: 1500 USD

Comparaison des objets de valeur

Dans cet exemple, l'objet Money est immuable, et deux instances de Money avec les mêmes valeurs (montant et devise) sont considérées comme égales, même s'il s'agit d'objets distincts.

$payment1 = new Money(1000, 'USD');
$payment2 = new Money(1000, 'USD');

var_dump($payment1->equals($payment2)); // true

Les Value Objects sont définis par leurs attributs et non par leur identité, contrairement aux entités dans un modèle de domaine qui ont un identifiant unique.

Pourquoi utiliser des Value Objects ?

  1. Immuabilité : Les Value Objects sont immuables, ce qui signifie que leur état ne peut pas être modifié une fois qu'ils sont créés. Cela rend le code plus sûr, car cela évite les effets de bord inattendus.

  2. Clarté et sécurité : Utiliser un Value Object pour des concepts spécifiques (comme une monnaie, une adresse ou une date) permet de mieux représenter la réalité métier. Cela empêche des erreurs de type ou des utilisations incorrectes (comme additionner des montants avec des devises différentes).

  3. Comparaison par valeur : Les Value Objects sont définis par leurs attributs, et non par leur identité. Deux objets de valeur sont égaux s'ils ont les mêmes attributs, indépendamment de leur instance.

  4. Réutilisabilité : Une fois créés, les Value Objects peuvent être réutilisés dans plusieurs parties de l'application. Par exemple, un objet Money peut être utilisé à la fois pour les paiements, les factures, et les remboursements.

  5. Meilleure modélisation du domaine : Les Value Objects permettent de mieux modéliser les concepts métiers complexes. Plutôt que d'utiliser des types primitifs (comme des entiers ou des chaînes de caractères), tu peux créer des objets qui encapsulent la logique métier autour de ces valeurs.

Autres exemples de Value Objects

  1. Adresse : Représenter une adresse en tant qu'objet avec des attributs comme la rue, la ville, et le code postal.
   $address = new Address('123 Main St', 'New York', '10001');
  1. Date : Manipuler des dates en tant qu'objet, avec des méthodes pour comparer, ajouter des jours, etc.
   $date1 = new Date('2024-10-01');
   $date2 = new Date('2024-10-05');
   $daysDifference = $date1->diffInDays($date2);
  1. Nom complet : Modéliser un nom complet avec les parties prénom et nom de famille, avec la possibilité de manipuler ces parties.
   $name = new FullName('John', 'Doe');

Avantages du Value Object Pattern

  1. Sécurité : Les Value Objects permettent d'encapsuler des concepts métiers importants (comme la monnaie, les adresses, etc.), garantissant que seules des opérations valides sont effectuées sur ces objets.
  2. Immuabilité : En étant immuables, les Value Objects éliminent les effets de bord, ce qui rend le code plus sûr et prévisible.
  3. Comparaison facile : Les Value Objects peuvent être comparés par valeur plutôt que par référence, ce qui simplifie la logique métier lors de la vérification de l'égalité.
  4. Représentation claire des concepts métiers : Utiliser des objets de valeur améliore la lisibilité et la compréhension du code, car les concepts métiers sont modélisés explicitement.

Inconvénients du Value Object Pattern

  1. Création d'instances : Comme les Value Objects sont immuables, chaque modification d'un objet implique la création d'une nouvelle instance, ce qui peut potentiellement affecter les performances si tu manipules de très grandes quantités d'objets.

  2. Gestion de la persistance : Les Value Objects ne sont pas forcément liés à une entité dans la base de données. Il peut donc être nécessaire

    de les manipuler de manière explicite lors de la persistance (en les sérialisant, par exemple).

Conclusion

Le Value Object Pattern est un excellent moyen de modéliser des concepts métiers spécifiques dans les applications Laravel et PHP. En encapsulant des valeurs dans des objets immuables et bien définis, ce pattern permet de rendre le code plus sûr, plus modulaire et plus expressif. Utiliser des Value Objects aide à mieux représenter les idées métier dans ton code, tout en réduisant les erreurs liées aux types primitifs mal utilisés. C'est un pattern particulièrement utile dans les applications qui nécessitent de manipuler des concepts complexes comme la monnaie, les adresses, ou les dates.