3.9. Specification

3.9.1. Rôle

Construire une spécification claire des règles de gestion, à laquelle les objets peuvent être comparés. La classe de spécification composite possède une méthode appelée isSatisfiedBy qui renvoie soit vrai soit faux selon que l’objet soit conforme ou non aux règles de gestion.

3.9.2. Exemples

3.9.3. Diagramme UML

Alt Specification UML Diagram

3.9.4. Code

Vous pouvez également trouver ce code sur GitHub

Item.php

 1<?php
 2
 3declare(strict_types=1);
 4
 5namespace DesignPatterns\Behavioral\Specification;
 6
 7class Item
 8{
 9    public function __construct(private float $price)
10    {
11    }
12
13    public function getPrice(): float
14    {
15        return $this->price;
16    }
17}

Specification.php

 1<?php
 2
 3declare(strict_types=1);
 4
 5namespace DesignPatterns\Behavioral\Specification;
 6
 7interface Specification
 8{
 9    public function isSatisfiedBy(Item $item): bool;
10}

OrSpecification.php

 1<?php
 2
 3declare(strict_types=1);
 4
 5namespace DesignPatterns\Behavioral\Specification;
 6
 7class OrSpecification implements Specification
 8{
 9    /**
10     * @var Specification[]
11     */
12    private array $specifications;
13
14    /**
15     * @param Specification[] $specifications
16     */
17    public function __construct(Specification ...$specifications)
18    {
19        $this->specifications = $specifications;
20    }
21
22    /*
23     * if at least one specification is true, return true, else return false
24     */
25    public function isSatisfiedBy(Item $item): bool
26    {
27        foreach ($this->specifications as $specification) {
28            if ($specification->isSatisfiedBy($item)) {
29                return true;
30            }
31        }
32
33        return false;
34    }
35}

PriceSpecification.php

 1<?php
 2
 3declare(strict_types=1);
 4
 5namespace DesignPatterns\Behavioral\Specification;
 6
 7class PriceSpecification implements Specification
 8{
 9    public function __construct(private ?float $minPrice, private ?float $maxPrice)
10    {
11    }
12
13    public function isSatisfiedBy(Item $item): bool
14    {
15        if ($this->maxPrice !== null && $item->getPrice() > $this->maxPrice) {
16            return false;
17        }
18
19        if ($this->minPrice !== null && $item->getPrice() < $this->minPrice) {
20            return false;
21        }
22
23        return true;
24    }
25}

AndSpecification.php

 1<?php
 2
 3declare(strict_types=1);
 4
 5namespace DesignPatterns\Behavioral\Specification;
 6
 7class AndSpecification implements Specification
 8{
 9    /**
10     * @var Specification[]
11     */
12    private array $specifications;
13
14    /**
15     * @param Specification[] $specifications
16     */
17    public function __construct(Specification ...$specifications)
18    {
19        $this->specifications = $specifications;
20    }
21
22    /**
23     * if at least one specification is false, return false, else return true.
24     */
25    public function isSatisfiedBy(Item $item): bool
26    {
27        foreach ($this->specifications as $specification) {
28            if (!$specification->isSatisfiedBy($item)) {
29                return false;
30            }
31        }
32
33        return true;
34    }
35}

NotSpecification.php

 1<?php
 2
 3declare(strict_types=1);
 4
 5namespace DesignPatterns\Behavioral\Specification;
 6
 7class NotSpecification implements Specification
 8{
 9    public function __construct(private Specification $specification)
10    {
11    }
12
13    public function isSatisfiedBy(Item $item): bool
14    {
15        return !$this->specification->isSatisfiedBy($item);
16    }
17}

3.9.5. Test

Tests/SpecificationTest.php

 1<?php
 2
 3declare(strict_types=1);
 4
 5namespace DesignPatterns\Behavioral\Specification\Tests;
 6
 7use DesignPatterns\Behavioral\Specification\Item;
 8use DesignPatterns\Behavioral\Specification\NotSpecification;
 9use DesignPatterns\Behavioral\Specification\OrSpecification;
10use DesignPatterns\Behavioral\Specification\AndSpecification;
11use DesignPatterns\Behavioral\Specification\PriceSpecification;
12use PHPUnit\Framework\TestCase;
13
14class SpecificationTest extends TestCase
15{
16    public function testCanOr()
17    {
18        $spec1 = new PriceSpecification(50, 99);
19        $spec2 = new PriceSpecification(101, 200);
20
21        $orSpec = new OrSpecification($spec1, $spec2);
22
23        $this->assertFalse($orSpec->isSatisfiedBy(new Item(100)));
24        $this->assertTrue($orSpec->isSatisfiedBy(new Item(51)));
25        $this->assertTrue($orSpec->isSatisfiedBy(new Item(150)));
26    }
27
28    public function testCanAnd()
29    {
30        $spec1 = new PriceSpecification(50, 100);
31        $spec2 = new PriceSpecification(80, 200);
32
33        $andSpec = new AndSpecification($spec1, $spec2);
34
35        $this->assertFalse($andSpec->isSatisfiedBy(new Item(150)));
36        $this->assertFalse($andSpec->isSatisfiedBy(new Item(1)));
37        $this->assertFalse($andSpec->isSatisfiedBy(new Item(51)));
38        $this->assertTrue($andSpec->isSatisfiedBy(new Item(100)));
39    }
40
41    public function testCanNot()
42    {
43        $spec1 = new PriceSpecification(50, 100);
44        $notSpec = new NotSpecification($spec1);
45
46        $this->assertTrue($notSpec->isSatisfiedBy(new Item(150)));
47        $this->assertFalse($notSpec->isSatisfiedBy(new Item(50)));
48    }
49}