Skip to content

Instantly share code, notes, and snippets.

@grachevko
Last active December 28, 2017 08:21
Show Gist options
  • Save grachevko/59662741ed2984f564364391c5230dfe to your computer and use it in GitHub Desktop.
Save grachevko/59662741ed2984f564364391c5230dfe to your computer and use it in GitHub Desktop.
<?php
declare(strict_types=1);
namespace App\Product\Quality;
use App\Entity\Product\Segment;
use Generator;
use InvalidArgumentException;
use Money\Money;
/**
* @author Konstantin Grachev <me@grachevko.ru>
*/
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();
}
}
}
<?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 <me@grachevko.ru>
*/
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