Last active
December 28, 2017 08:21
-
-
Save grachevko/59662741ed2984f564364391c5230dfe to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
declare(strict_types=1); | |
namespace App\Product\Quality; | |
use App\Entity\Product\Segment; | |
use Generator; | |
use InvalidArgumentException; | |
use Money\Money; | |
/** | |
* @author Konstantin Grachev <[email protected]> | |
*/ | |
final class SegmentAllocator | |
{ | |
private const ONE_HUNDRED_PERCENT = 100; | |
/** | |
* @var Money | |
*/ | |
private $current; | |
/** | |
* @var Money | |
*/ | |
private $next; | |
/** | |
* @var Segment | |
*/ | |
private $segment; | |
/** | |
* @var Generator | |
*/ | |
private $switcher; | |
public function __construct(Money $sum, array $map) | |
{ | |
$this->current = new Money(0, $sum->getCurrency()); | |
$this->next = new Money(0, $sum->getCurrency()); | |
$ratios = array_map(function ($item) { | |
return $item['ratio']; | |
}, $map); | |
if (self::ONE_HUNDRED_PERCENT !== array_sum($ratios)) { | |
throw new InvalidArgumentException( | |
sprintf('Sum of ratios must be %s', self::ONE_HUNDRED_PERCENT) | |
); | |
} | |
$parts = $sum->allocate($ratios); | |
$this->switcher = call_user_func(function () use ($map, $parts) { | |
foreach ($parts as $key => $part) { | |
$this->next = $this->next->add($part); | |
$this->segment = $map[$key]['segment']; | |
yield; | |
} | |
}); | |
$this->switcher->current(); | |
} | |
public function allocate(Money $money): Segment | |
{ | |
$currency = $this->current->getCurrency(); | |
if (!$currency->equals($money->getCurrency())) { | |
throw new InvalidArgumentException( | |
sprintf('This instance of %s accept only %s currency.', __CLASS__, $currency->getCode()) | |
); | |
} | |
$this->increment($money); | |
return $this->segment; | |
} | |
private function increment(Money $money): void | |
{ | |
$this->current = $this->current->add($money); | |
if ($this->current->greaterThan($this->next)) { | |
$this->switcher->next(); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
declare(strict_types=1); | |
namespace App\Tests\Product\Quality; | |
use App\Entity\Product\Segment; | |
use App\Product\Quality\SegmentAllocator; | |
use InvalidArgumentException; | |
use Money\Currency; | |
use Money\Money; | |
use PHPUnit\Framework\TestCase; | |
/** | |
* @author Konstantin Grachev <[email protected]> | |
*/ | |
final class SegmentAllocatorTest extends TestCase | |
{ | |
public function testRatioSum(): void | |
{ | |
$this->expectException(InvalidArgumentException::class); | |
new SegmentAllocator(new Money(0, new Currency('RUB')), [ | |
['ratio' => 99, 'segment' => Segment::medium()], | |
]); | |
} | |
public function testAllocate(): void | |
{ | |
$currency = new Currency('RUB'); | |
$allocator = new SegmentAllocator(new Money(100, $currency), [ | |
['ratio' => 25, 'segment' => Segment::eco()], | |
['ratio' => 25, 'segment' => Segment::medium()], | |
['ratio' => 25, 'segment' => Segment::high()], | |
['ratio' => 25, 'segment' => Segment::exclusive()], | |
]); | |
$this->assertSame(Segment::eco()->getName(), $allocator->allocate(new Money(10, $currency))->getName()); | |
$this->assertSame(Segment::eco()->getName(), $allocator->allocate(new Money(10, $currency))->getName()); | |
$this->assertSame(Segment::medium()->getName(), $allocator->allocate(new Money(10, $currency))->getName()); | |
$this->assertSame(Segment::medium()->getName(), $allocator->allocate(new Money(10, $currency))->getName()); | |
$this->assertSame(Segment::medium()->getName(), $allocator->allocate(new Money(10, $currency))->getName()); | |
$this->assertSame(Segment::high()->getName(), $allocator->allocate(new Money(10, $currency))->getName()); | |
$this->assertSame(Segment::high()->getName(), $allocator->allocate(new Money(10, $currency))->getName()); | |
$this->assertSame(Segment::exclusive()->getName(), $allocator->allocate(new Money(10, $currency))->getName()); | |
$this->assertSame(Segment::exclusive()->getName(), $allocator->allocate(new Money(100, $currency))->getName()); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment