3.9. Specification¶
3.9.1. Purpose¶
Builds a clear specification of business rules, where objects can be
checked against. The composite specification class has one method called
isSatisfiedBy
that returns either true or false depending on whether
the given object satisfies the specification.
3.9.3. UML Diagram¶

3.9.4. Code¶
You can also find this code on GitHub
Item.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <?php
declare(strict_types=1);
namespace DesignPatterns\Behavioral\Specification;
class Item
{
public function __construct(private float $price)
{
}
public function getPrice(): float
{
return $this->price;
}
}
|
Specification.php
1 2 3 4 5 6 7 8 9 10 | <?php
declare(strict_types=1);
namespace DesignPatterns\Behavioral\Specification;
interface Specification
{
public function isSatisfiedBy(Item $item): bool;
}
|
OrSpecification.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | <?php
declare(strict_types=1);
namespace DesignPatterns\Behavioral\Specification;
class OrSpecification implements Specification
{
/**
* @var Specification[]
*/
private array $specifications;
/**
* @param Specification[] $specifications
*/
public function __construct(Specification ...$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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | <?php
declare(strict_types=1);
namespace DesignPatterns\Behavioral\Specification;
class PriceSpecification implements Specification
{
public function __construct(private ?float $minPrice, private ?float $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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | <?php
declare(strict_types=1);
namespace DesignPatterns\Behavioral\Specification;
class AndSpecification implements Specification
{
/**
* @var Specification[]
*/
private array $specifications;
/**
* @param Specification[] $specifications
*/
public function __construct(Specification ...$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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <?php
declare(strict_types=1);
namespace DesignPatterns\Behavioral\Specification;
class NotSpecification implements Specification
{
public function __construct(private Specification $specification)
{
}
public function isSatisfiedBy(Item $item): bool
{
return !$this->specification->isSatisfiedBy($item);
}
}
|
3.9.5. Test¶
Tests/SpecificationTest.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | <?php
declare(strict_types=1);
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)));
}
}
|