3.8. Specification

3.8.1. Purpose

Builds a clear specification of business rules, where objects can bechecked against. The composite specification class has one method calledisSatisfiedBy that returns either true or false depending on whetherthe given object satisfies the specification.

3.8.2. Examples

3.8.3. UML Diagram

Alt Specification UML Diagram

3.8.4. Code

You can also find this code on GitHub

Item.php

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

SpecificationInterface.php

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

OrSpecification.php

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

PriceSpecification.php

  1. <?php
  2.  
  3. namespace DesignPatterns\Behavioral\Specification;
  4.  
  5. class PriceSpecification implements SpecificationInterface
  6. {
  7. /**
  8. * @var float|null
  9. */
  10. private $maxPrice;
  11.  
  12. /**
  13. * @var float|null
  14. */
  15. private $minPrice;
  16.  
  17. /**
  18. * @param float $minPrice
  19. * @param float $maxPrice
  20. */
  21. public function __construct($minPrice, $maxPrice)
  22. {
  23. $this->minPrice = $minPrice;
  24. $this->maxPrice = $maxPrice;
  25. }
  26.  
  27. public function isSatisfiedBy(Item $item): bool
  28. {
  29. if ($this->maxPrice !== null && $item->getPrice() > $this->maxPrice) {
  30. return false;
  31. }
  32.  
  33. if ($this->minPrice !== null && $item->getPrice() < $this->minPrice) {
  34. return false;
  35. }
  36.  
  37. return true;
  38. }
  39. }

AndSpecification.php

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

NotSpecification.php

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

3.8.5. Test

Tests/SpecificationTest.php

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