Digital solutions
Design Patterns avec Laravel

Specification Pattern

  • Type : Comportemental
  • Difficulté : 7/10
  • Définition succincte : Le Specification Pattern est un design pattern comportemental qui permet d'encapsuler des critères de validation ou des règles d'affaires dans des objets réutilisables, appelés specifications. Ce pattern facilite la composition de règles complexes et permet d'évaluer si un objet satisfait à certaines conditions. Il est souvent utilisé pour gérer des règles métier complexes ou des critères de recherche dynamiques.

Objectif du Specification Pattern

Le Specification Pattern a pour but de séparer les règles métier des objets eux-mêmes et de permettre une composition dynamique des conditions ou critères. Il est particulièrement utile lorsque tu dois appliquer plusieurs conditions complexes sur des objets ou entités, tout en facilitant la réutilisation et la modification de ces règles.

Structure du Specification Pattern

  • Specification : Interface ou classe abstraite qui définit une méthode isSatisfiedBy() pour vérifier si un objet satisfait la spécification.
  • ConcreteSpecification : Implémentation concrète de la Specification qui vérifie une condition spécifique.
  • CompositeSpecification : Spécifications qui permettent de composer d'autres spécifications à l'aide de combinaisons logiques telles que ET, OU, ou NON.

Implémentation avec Laravel

Prenons un exemple dans une application Laravel où tu souhaites appliquer des règles de validation complexes à des utilisateurs qui s'inscrivent pour un service. Les utilisateurs doivent satisfaire à plusieurs conditions, comme être majeurs, résider dans une certaine région, et avoir accepté les conditions d'utilisation. Le Specification Pattern te permet d'encapsuler ces règles et de les composer dynamiquement selon les besoins.

a) Créer l'interface Specification (Specification)

L'interface Specification déclare la méthode isSatisfiedBy() qui permet de vérifier si un objet satisfait la condition de la spécification.

// app/Contracts/Specification.php

namespace App\Contracts;

interface Specification
{
    public function isSatisfiedBy($candidate): bool;
}

b) Implémenter des ConcreteSpecifications

Les classes concrètes de spécification vont encapsuler des règles spécifiques à vérifier. Dans cet exemple, nous allons créer trois spécifications : AgeSpecification, RegionSpecification, et TermsAcceptedSpecification.

1. Spécification d'âge (AgeSpecification)

Vérifie si l'utilisateur a l'âge requis.

// app/Specifications/AgeSpecification.php

namespace App\Specifications;

use App\Contracts\Specification;

class AgeSpecification implements Specification
{
    private $minAge;

    public function __construct(int $minAge)
    {
        $this->minAge = $minAge;
    }

    public function isSatisfiedBy($candidate): bool
    {
        return $candidate->age >= $this->minAge;
    }
}
2. Spécification de région (RegionSpecification)

Vérifie si l'utilisateur réside dans une région donnée.

// app/Specifications/RegionSpecification.php

namespace App\Specifications;

use App\Contracts\Specification;

class RegionSpecification implements Specification
{
    private $allowedRegion;

    public function __construct(string $allowedRegion)
    {
        $this->allowedRegion = $allowedRegion;
    }

    public function isSatisfiedBy($candidate): bool
    {
        return $candidate->region === $this->allowedRegion;
    }
}
3. Spécification d'acceptation des conditions (TermsAcceptedSpecification)

Vérifie si l'utilisateur a accepté les conditions d'utilisation.

// app/Specifications/TermsAcceptedSpecification.php

namespace App\Specifications;

use App\Contracts\Specification;

class TermsAcceptedSpecification implements Specification
{
    public function isSatisfiedBy($candidate): bool
    {
        return $candidate->termsAccepted === true;
    }
}

c) Créer des CompositeSpecifications

Ces spécifications permettent de combiner plusieurs règles à l'aide d'opérateurs logiques. Voici deux exemples : AndSpecification et OrSpecification.

1. Spécification AndSpecification (ET logique)

Cette spécification vérifie si deux autres spécifications sont satisfaites.

// app/Specifications/AndSpecification.php

namespace App\Specifications;

use App\Contracts\Specification;

class AndSpecification implements Specification
{
    private $spec1;
    private $spec2;

    public function __construct(Specification $spec1, Specification $spec2)
    {
        $this->spec1 = $spec1;
        $this->spec2 = $spec2;
    }

    public function isSatisfiedBy($candidate): bool
    {
        return $this->spec1->isSatisfiedBy($candidate) && $this->spec2->isSatisfiedBy($candidate);
    }
}
2. Spécification OrSpecification (OU logique)

Cette spécification vérifie si l'une des deux spécifications est satisfaite.

// app/Specifications/OrSpecification.php

namespace App\Specifications;

use App\Contracts\Specification;

class OrSpecification implements Specification
{
    private $spec1;
    private $spec2;

    public function __construct(Specification $spec1, Specification $spec2)
    {
        $this->spec1 = $spec1;
        $this->spec2 = $spec2;
    }

    public function isSatisfiedBy($candidate): bool
    {
        return $this->spec1->isSatisfiedBy($candidate) || $this->spec2->isSatisfiedBy($candidate);
    }
}

d) Utilisation du Specification Pattern dans une commande CLI

Nous allons maintenant créer une commande CLI pour tester l'inscription des utilisateurs selon différentes spécifications.

1. Classe utilisateur

Un simple objet représentant un utilisateur.

// app/Models/User.php

namespace App\Models;

class User
{
    public $age;
    public $region;
    public $termsAccepted;

    public function __construct(int $age, string $region, bool $termsAccepted)
    {
        $this->age = $age;
        $this->region = $region;
        $this->termsAccepted = $termsAccepted;
    }
}
2. Commande CheckUserEligibility

La commande vérifie si un utilisateur satisfait aux critères combinés des spécifications.

// app/Console/Commands/CheckUserEligibility.php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use App\Models\User;
use App\Specifications\AgeSpecification;
use App\Specifications\RegionSpecification;
use App\Specifications\TermsAcceptedSpecification;
use App\Specifications\AndSpecification;

class CheckUserEligibility extends Command
{
    protected $signature = 'user:check-eligibility';
    protected $description = 'Vérifie si un utilisateur est éligible à s\'inscrire selon des critères de spécifications';

    public function handle()
    {
        // Créer un utilisateur fictif
        $user = new User(22, 'Europe', true);

        // Définir les spécifications
        $ageSpec = new AgeSpecification(18);
        $regionSpec = new RegionSpecification('Europe');
        $termsSpec = new TermsAcceptedSpecification();

        // Combiner les spécifications avec "AND"
        $eligibilitySpec = new AndSpecification($ageSpec, new AndSpecification($regionSpec, $termsSpec));

        // Vérifier si l'utilisateur satisfait les critères
        if ($eligibilitySpec->isSatisfiedBy($user)) {
            $this->info('L\'utilisateur satisfait toutes les conditions d\'inscription.');
        } else {
            $this->error('L\'utilisateur ne satisfait pas aux critères d\'inscription.');
        }
    }
}

e) Exécution de la commande

Tu peux exécuter cette commande pour vérifier si l'utilisateur satisfait aux critères combinés d'âge, de région et d'acceptation des conditions.

php artisan user:check-eligibility

Résultat attendu :

L'utilisateur satisfait toutes les conditions d'inscription.

Avantages du Specification Pattern

  1. Réutilisabilité : Les spécifications encapsulent des règles réutilisables qui peuvent être appliquées à différents objets ou situations.
  2. Modularité : Ce pattern permet de séparer les règles métier des objets eux-mêmes, ce qui facilite la maintenance et l'évolution des règles.
  3. Composition flexible : Les spécifications peuvent être combinées logiquement à l'aide de AND, OR, ou NOT, permettant ainsi de créer des critères complexes à partir de règles simples.

Inconvénients du Specification Pattern

  1. Surcharge de classes : Pour chaque règle ou combinaison de règles, tu dois créer une nouvelle classe, ce qui peut entraîner une explosion du nombre de classes.
  2. Complexité : La composition de spécifications multiples peut devenir complexe à gérer et à maintenir, surtout dans de grands systèmes avec de nombreuses règles métier.

Conclusion

Le Specification Pattern est un excellent choix pour encapsuler et combiner des règles métier complexes dans des applications Laravel. Il te permet de composer dynamiquement des critères et de les appliquer à des objets ou des

entités sans altérer leur structure. Ce pattern est particulièrement utile dans les systèmes de validation complexes ou les filtres dynamiques, offrant une modularité et une flexibilité qui facilitent la maintenance et l'extension du code.