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
3.8.4. Code
You can also find this code on GitHub
Item.php
- <?php
- namespace DesignPatterns\Behavioral\Specification;
- class Item
- {
- /**
- * @var float
- */
- private $price;
- public function __construct(float $price)
- {
- $this->price = $price;
- }
- public function getPrice(): float
- {
- return $this->price;
- }
- }
SpecificationInterface.php
- <?php
- namespace DesignPatterns\Behavioral\Specification;
- interface SpecificationInterface
- {
- public function isSatisfiedBy(Item $item): bool;
- }
OrSpecification.php
- <?php
- namespace DesignPatterns\Behavioral\Specification;
- class OrSpecification implements SpecificationInterface
- {
- /**
- * @var SpecificationInterface[]
- */
- private $specifications;
- /**
- * @param SpecificationInterface[] ...$specifications
- */
- public function __construct(SpecificationInterface ...$specifications)
- {
- $this->specifications = $specifications;
- }
- /**
- * if at least one specification is true, return true, else return false
- */
- public function isSatisfiedBy(Item $item): bool
- {
- foreach ($this->specifications as $specification) {
- if ($specification->isSatisfiedBy($item)) {
- return true;
- }
- }
- return false;
- }
- }
PriceSpecification.php
- <?php
- namespace DesignPatterns\Behavioral\Specification;
- class PriceSpecification implements SpecificationInterface
- {
- /**
- * @var float|null
- */
- private $maxPrice;
- /**
- * @var float|null
- */
- private $minPrice;
- /**
- * @param float $minPrice
- * @param float $maxPrice
- */
- public function __construct($minPrice, $maxPrice)
- {
- $this->minPrice = $minPrice;
- $this->maxPrice = $maxPrice;
- }
- public function isSatisfiedBy(Item $item): bool
- {
- if ($this->maxPrice !== null && $item->getPrice() > $this->maxPrice) {
- return false;
- }
- if ($this->minPrice !== null && $item->getPrice() < $this->minPrice) {
- return false;
- }
- return true;
- }
- }
AndSpecification.php
- <?php
- namespace DesignPatterns\Behavioral\Specification;
- class AndSpecification implements SpecificationInterface
- {
- /**
- * @var SpecificationInterface[]
- */
- private $specifications;
- /**
- * @param SpecificationInterface[] ...$specifications
- */
- public function __construct(SpecificationInterface ...$specifications)
- {
- $this->specifications = $specifications;
- }
- /**
- * if at least one specification is false, return false, else return true.
- */
- public function isSatisfiedBy(Item $item): bool
- {
- foreach ($this->specifications as $specification) {
- if (!$specification->isSatisfiedBy($item)) {
- return false;
- }
- }
- return true;
- }
- }
NotSpecification.php
- <?php
- namespace DesignPatterns\Behavioral\Specification;
- class NotSpecification implements SpecificationInterface
- {
- /**
- * @var SpecificationInterface
- */
- private $specification;
- public function __construct(SpecificationInterface $specification)
- {
- $this->specification = $specification;
- }
- public function isSatisfiedBy(Item $item): bool
- {
- return !$this->specification->isSatisfiedBy($item);
- }
- }
3.8.5. Test
Tests/SpecificationTest.php
- <?php
- namespace DesignPatterns\Behavioral\Specification\Tests;
- use DesignPatterns\Behavioral\Specification\Item;
- use DesignPatterns\Behavioral\Specification\NotSpecification;
- use DesignPatterns\Behavioral\Specification\OrSpecification;
- use DesignPatterns\Behavioral\Specification\AndSpecification;
- use DesignPatterns\Behavioral\Specification\PriceSpecification;
- use PHPUnit\Framework\TestCase;
- class SpecificationTest extends TestCase
- {
- public function testCanOr()
- {
- $spec1 = new PriceSpecification(50, 99);
- $spec2 = new PriceSpecification(101, 200);
- $orSpec = new OrSpecification($spec1, $spec2);
- $this->assertFalse($orSpec->isSatisfiedBy(new Item(100)));
- $this->assertTrue($orSpec->isSatisfiedBy(new Item(51)));
- $this->assertTrue($orSpec->isSatisfiedBy(new Item(150)));
- }
- public function testCanAnd()
- {
- $spec1 = new PriceSpecification(50, 100);
- $spec2 = new PriceSpecification(80, 200);
- $andSpec = new AndSpecification($spec1, $spec2);
- $this->assertFalse($andSpec->isSatisfiedBy(new Item(150)));
- $this->assertFalse($andSpec->isSatisfiedBy(new Item(1)));
- $this->assertFalse($andSpec->isSatisfiedBy(new Item(51)));
- $this->assertTrue($andSpec->isSatisfiedBy(new Item(100)));
- }
- public function testCanNot()
- {
- $spec1 = new PriceSpecification(50, 100);
- $notSpec = new NotSpecification($spec1);
- $this->assertTrue($notSpec->isSatisfiedBy(new Item(150)));
- $this->assertFalse($notSpec->isSatisfiedBy(new Item(50)));
- }
- }