specification Спецификация (англ. Specification) — это шаблон проектирования, посредством которого представление правил бизнес логики может быть преобразовано в виде цепочки объектов, связанных операциями булевой логики.

Этот шаблон выделяет такие спецификации (правила) в бизнес логике, которые подходят для “сцепления” с другими спецификациями. Объект бизнес логики наследует свою функциональность от абстрактного агрегирующего класса SpecificationInterface, который содержит всего один метод SpecificationInterface->IsSatisfiedBy(), возвращающий булево значение. После инстанцирования, объект объединяется в цепочку с другими объектами. В результате, не теряя гибкости в настройке, бизнес логики, мы можем с лёгкостью добавлять новые правила.

  • AndSpecification.php
                  
    <?php
    
    namespace DesignPatterns\Behavioral\Specification;
    
    /**
     * Class AndSpecification
     * @package DesignPatterns\Behavioral\Specification
     */
    class AndSpecification implements SpecificationInterface
    {
        /**
         * @var SpecificationInterface[]
         */
        private $specifications;
    
        /**
         * @param SpecificationInterface[] ...$specifications
         */
        public function __construct(SpecificationInterface ...$specifications)
        {
            $this->specifications = $specifications;
        }
    
        /**
         * @param Item $item
         *
         * @return bool
         */
        public function isSatisfiedBy(Item $item): bool
        {
            $satisfied = [];
    
            foreach ($this->specifications as $specification) {
                $satisfied[] = $specification->isSatisfiedBy($item);
            }
    
            return !in_array(false, $satisfied);
        }
    }
    
                  
  • Item.php
                  
    <?php
    
    namespace DesignPatterns\Behavioral\Specification;
    
    /**
     * Class Item
     * @package DesignPatterns\Behavioral\Specification
     */
    class Item
    {
        /**
         * @var float
         */
        private $price;
    
        /**
         * Item constructor.
         *
         * @param float $price
         */
        public function __construct(float $price)
        {
            $this->price = $price;
        }
    
        /**
         * @return float
         */
        public function getPrice(): float
        {
            return $this->price;
        }
    }
    
                  
  • NotSpecification.php
                  
    <?php
    
    namespace DesignPatterns\Behavioral\Specification;
    
    /**
     * Class NotSpecification
     * @package DesignPatterns\Behavioral\Specification
     */
    class NotSpecification implements SpecificationInterface
    {
        /**
         * @var SpecificationInterface
         */
        private $specification;
    
        /**
         * NotSpecification constructor.
         *
         * @param SpecificationInterface $specification
         */
        public function __construct(SpecificationInterface $specification)
        {
            $this->specification = $specification;
        }
    
        /**
         * @param Item $item
         *
         * @return bool
         */
        public function isSatisfiedBy(Item $item): bool
        {
            return !$this->specification->isSatisfiedBy($item);
        }
    }
    
                  
  • OrSpecification.php
                  
    <?php
    
    namespace DesignPatterns\Behavioral\Specification;
    
    /**
     * Class OrSpecification
     * @package DesignPatterns\Behavioral\Specification
     */
    class OrSpecification implements SpecificationInterface
    {
        /**
         * @var SpecificationInterface[]
         */
        private $specifications;
    
        /**
         * @param SpecificationInterface[] ...$specifications
         */
        public function __construct(SpecificationInterface ...$specifications)
        {
            $this->specifications = $specifications;
        }
    
        /**
         * @param Item $item
         *
         * @return bool
         */
        public function isSatisfiedBy(Item $item): bool
        {
            $satisfied = [];
    
            foreach ($this->specifications as $specification) {
                $satisfied[] = $specification->isSatisfiedBy($item);
            }
    
            return in_array(true, $satisfied);
        }
    }
    
                  
  • PriceSpecification.php
                  
    <?php
    
    namespace DesignPatterns\Behavioral\Specification;
    
    /**
     * Class PriceSpecification
     * @package DesignPatterns\Behavioral\Specification
     */
    class PriceSpecification implements SpecificationInterface
    {
        /**
         * @var float
         */
        private $maxPrice;
    
        /**
         * @var float
         */
        private $minPrice;
    
        /**
         * @param float $minPrice
         * @param float $maxPrice
         */
        public function __construct(float $minPrice, float $maxPrice)
        {
            $this->minPrice = $minPrice;
            $this->maxPrice = $maxPrice;
        }
    
        /**
         * @param Item $item
         *
         * @return bool
         */
        public function isSatisfiedBy(Item $item): bool
        {
            if ($item->getPrice() > $this->maxPrice) {
                return false;
            }
    
            if ($item->getPrice() < $this->minPrice) {
                return false;
            }
    
            return true;
        }
    }
    
                  
  • SpecificationInterface.php
                  
    <?php
    
    namespace DesignPatterns\Behavioral\Specification;
    
    /**
     * Interface SpecificationInterface
     * @package DesignPatterns\Behavioral\Specification
     */
    interface SpecificationInterface
    {
        /**
         * @param Item $item
         *
         * @return bool
         */
        public function isSatisfiedBy(Item $item): bool;
    }
    
                  
  • Test case:

    SpecificationTest.php
      
    <?php
    namespace DesignPatterns\Tests\Behavioral\Specification;
    
    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 PHPUnit_Framework_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);
    
            $orSpec = new AndSpecification($spec1, $spec2);
    
            $this->assertFalse($orSpec->isSatisfiedBy(new Item(150)));
            $this->assertFalse($orSpec->isSatisfiedBy(new Item(1)));
            $this->assertFalse($orSpec->isSatisfiedBy(new Item(51)));
            $this->assertTrue($orSpec->isSatisfiedBy(new Item(100)));
        }
    
        public function testCanNot()
        {
            $spec1 = new PriceSpecification(50, 100);
            $orSpec = new NotSpecification($spec1);
    
            $this->assertTrue($orSpec->isSatisfiedBy(new Item(150)));
            $this->assertFalse($orSpec->isSatisfiedBy(new Item(50)));
        }
    }