hansanghyeon / spec
Specification pattern
dev-main
2024-02-07 16:09 UTC
Requires (Dev)
- phpunit/phpunit: ^11
This package is auto-updated.
Last update: 2024-06-07 16:54:04 UTC
README
Specification pattern - Wikipedia
How to use
composer require hansanghyeon/spec
Example
<?php declare(strict_types=1); namespace Hansanghyeon\Spec; final class Product { public $isNew; public $name; public $price; public $color; public function __construct(array $data) { $this->isNew = $data['isNew']; $this->name = $data['name']; $this->price = $data['price']; $this->color = $data['color']; } }
<?php declare(strict_types=1); use Hansanghyeon\Spec\Spec; use Hansanghyeon\Spec\Product; use PHPUnit\Framework\TestCase; function pipe(...$functions) { return function ($data) use ($functions) { return array_reduce($functions, function ($carry, $function) { return $function($carry); }, $data); }; } class ProductSpec { public static function getSpecs(): array { // 새롭게 추가된 상품인지 확인하는 스펙 $isNewSpec = new Spec(function ($candidate) { return $candidate->isNew === true; }); $isHighPriceSpec = new Spec(function ($candidate) { return $candidate->price >= 200_000; }); // 새롭게 추가된 상품이 아니고 기존 상품이면서 상품의 가격이 200,000원 이상인지 확인하는 스펙 $isOriginalAndHighPriceSpec = $isNewSpec->not()->and($isHighPriceSpec); return [ 'isNewSpec' => $isNewSpec, 'isHighPriceSpec' => $isHighPriceSpec, 'isOriginalAndHighPriceSpec' => $isOriginalAndHighPriceSpec, ]; } } final class ProductSpecTest extends TestCase { private $products; private $specs; protected function setUp(): void { parent::setUp(); // 스펙 객체들을 스태틱 메서드를 통해 가져옴 $this->specs = ProductSpec::getSpecs(); $this->products = array( new Product([ 'isNew' => true, 'name' => '피카츄', 'price' => 100_000, 'color' => 'black' ]), new Product([ 'isNew' => false, 'name' => '라이츄', 'price' => 150_000, 'color' => 'red' ]), new Product([ 'isNew' => false, 'name' => '파이리', 'price' => 200_000, 'color' => 'white' ]), new Product([ 'isNew' => true, 'name' => '꼬북이', 'price' => 250_000, 'color' => 'black' ]), ); } public function testIsNewSpecFiltering() { $isNewSpec = $this->specs['isNewSpec']; $filteredProducts = pipe( function ($products) use ($isNewSpec) { return array_filter($products, function ($product) use ($isNewSpec) { return $isNewSpec->isSatisfiedBy($product); }); }, 'array_values' )($this->products); $this->assertCount(2, $filteredProducts); // 피카츄와 꼬북이만이 isNew 스펙을 만족해야 합니다. $this->assertEquals('피카츄', $filteredProducts[0]->name); // $this->assertEquals('꼬북이', $filteredProducts[1]->name); } public function testIsHighPriceSpecFiltering() { $isHighPriceSpec = $this->specs['isHighPriceSpec']; $filteredProducts = pipe( function ($products) use ($isHighPriceSpec) { return array_filter($products, function ($product) use ($isHighPriceSpec) { return $isHighPriceSpec->isSatisfiedBy($product); }); }, 'array_values' )($this->products); $this->assertCount(2, $filteredProducts); // 파이리와 꼬북이만이 가격이 200,000원 이상을 만족해야 합니다. $this->assertEquals('파이리', $filteredProducts[0]->name); $this->assertEquals('꼬북이', $filteredProducts[1]->name); } public function testIsOriginalAndHighPriceSpecFiltering() { $isOriginalAndHighPriceSpec = $this->specs['isOriginalAndHighPriceSpec']; $filteredProducts = pipe( function ($products) use ($isOriginalAndHighPriceSpec) { return array_filter($products, function ($product) use ($isOriginalAndHighPriceSpec) { return $isOriginalAndHighPriceSpec->isSatisfiedBy($product); }); }, 'array_values' )($this->products); $this->assertCount(1, $filteredProducts); // 라이츄만이 새로운 상품이 아니고 가격이 200,000원 이상을 만족해야 합니다. $this->assertEquals('파이리', $filteredProducts[0]->name); } }