Skip to content

Commit

Permalink
Update strategies
Browse files Browse the repository at this point in the history
  • Loading branch information
Aleksandr Denisyuk authored and Aleksandr Denisyuk committed May 25, 2022
1 parent a5de389 commit 21dad6e
Show file tree
Hide file tree
Showing 27 changed files with 209 additions and 163 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ use Orangesoft\Throttler\Throttler;

$throttler = new Throttler(
new WeightedRoundRobinStrategy(
new InMemoryCounter(start: 0)
new InMemoryCounter(start: 0),
)
);

Expand Down
8 changes: 4 additions & 4 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use Orangesoft\Throttler\Throttler;

$throttler = new Throttler(
new WeightedRoundRobinStrategy(
new InMemoryCounter(start: 0)
new InMemoryCounter(start: 0),
)
);
```
Expand Down Expand Up @@ -135,7 +135,7 @@ FrequencyRandomStrategy has 2 not required options: frequency and depth. Frequen

```php
$throttler = new Throttler(
new FrequencyRandomStrategy(frequency: 0.8, depth: 0.2)
new FrequencyRandomStrategy(frequency: 0.8, depth: 0.2),
);

/** @var Node $node */
Expand Down Expand Up @@ -178,7 +178,7 @@ use Orangesoft\Throttler\Strategy\WeightedRoundRobinStrategy;
use Predis\Client;

$strategy = new RoundRobinStrategy(
new InMemoryCounter(start: 0)
new InMemoryCounter(start: 0),
);
```

Expand Down Expand Up @@ -209,7 +209,7 @@ In the example above, we wrote the counter with Redis.
/** @var Predis\Client $client */

$strategy = new WeightedRoundRobinStrategy(
new RedisCounter($client)
new RedisCounter($client),
);
```

Expand Down
31 changes: 15 additions & 16 deletions src/Collection/Collection.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,12 @@

final class Collection implements CollectionInterface
{
private \SplObjectStorage $storage;

private \SplObjectStorage $nodes;
private bool $isWeighted = true;

public function __construct(iterable $nodes = [])
{
$this->storage = new \SplObjectStorage();
$this->nodes = new \SplObjectStorage();

foreach ($nodes as $node) {
$this->addNode($node);
Expand All @@ -25,44 +24,44 @@ public function addNode(Node $node): self
$this->isWeighted = false;
}

$this->storage->attach($node);
$this->nodes->attach($node);

return $this;
}

public function getNode(int $index): Node
{
if ($index > $this->storage->count()) {
if ($index > $this->nodes->count()) {
throw new \InvalidArgumentException(
sprintf('Cannot find node at index %d', $index)
sprintf('Cannot find node at index "%d".', $index)
);
}

$this->storage->rewind();
$this->nodes->rewind();

while ($index--) {
$this->storage->next();
$this->nodes->next();
}

/** @var Node $node */
$node = $this->storage->current();
$node = $this->nodes->current();

return $node;
}

public function hasNode(Node $node): bool
{
return $this->storage->contains($node);
return $this->nodes->contains($node);
}

public function removeNode(Node $node): void
{
$this->storage->detach($node);
$this->nodes->detach($node);
}

public function purge(): void
{
$this->storage->removeAll($this->storage);
$this->nodes->removeAll($this->nodes);
}

public function isWeighted(): bool
Expand All @@ -72,24 +71,24 @@ public function isWeighted(): bool

public function isEmpty(): bool
{
return 0 === $this->storage->count();
return 0 === $this->nodes->count();
}

public function count(): int
{
return $this->storage->count();
return $this->nodes->count();
}

/**
* @return Node[]
*/
public function toArray(): array
{
return iterator_to_array($this->storage, false);
return iterator_to_array($this->nodes, false);
}

public function getIterator(): \Traversable
{
return $this->storage;
return $this->nodes;
}
}
4 changes: 4 additions & 0 deletions src/Collection/Exception/EmptyCollectionException.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,8 @@

class EmptyCollectionException extends \LogicException
{
public function __construct(string $message = 'Collection of nodes must not be empty.', int $code = 0, ?\Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
}
}
4 changes: 4 additions & 0 deletions src/Collection/Exception/UnweightedCollectionException.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,8 @@

class UnweightedCollectionException extends \LogicException
{
public function __construct(string $message = 'All nodes in the collection must be weighted.', int $code = 0, ?\Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
}
}
9 changes: 5 additions & 4 deletions src/Strategy/ClusterDetermineStrategy.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Orangesoft\Throttler\Strategy;

use Orangesoft\Throttler\Collection\CollectionInterface;
use Orangesoft\Throttler\Collection\Node;

final class ClusterDetermineStrategy implements StrategyInterface
{
Expand Down Expand Up @@ -34,21 +35,21 @@ public function __construct(ClusterSet ...$clusterSets)
}
}

public function getIndex(CollectionInterface $collection, array $context = []): int
public function getNode(CollectionInterface $collection, array $context = []): Node
{
if (!isset($context['cluster_name'])) {
throw new \RuntimeException('Required parameter "cluster_name" is missing.');
throw new \LogicException('Required parameter "cluster_name" is missing.');
}

if (!isset($this->clusterNames[$context['cluster_name']])) {
throw new \RuntimeException(
throw new \LogicException(
sprintf('Cluster name "%s" is undefined.', $context['cluster_name'])
);
}

/** @var StrategyInterface $strategy */
$strategy = $this->strategies[$this->clusterNames[$context['cluster_name']]];

return $strategy->getIndex($collection, $context);
return $strategy->getNode($collection, $context);
}
}
7 changes: 4 additions & 3 deletions src/Strategy/FrequencyRandomStrategy.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Orangesoft\Throttler\Collection\CollectionInterface;
use Orangesoft\Throttler\Collection\Exception\EmptyCollectionException;
use Orangesoft\Throttler\Collection\Node;

final class FrequencyRandomStrategy implements StrategyInterface
{
Expand All @@ -15,10 +16,10 @@ public function __construct(
) {
}

public function getIndex(CollectionInterface $collection, array $context = []): int
public function getNode(CollectionInterface $collection, array $context = []): Node
{
if ($collection->isEmpty()) {
throw new EmptyCollectionException('Collection of nodes must not be empty.');
throw new EmptyCollectionException();
}

$total = count($collection);
Expand All @@ -27,7 +28,7 @@ public function getIndex(CollectionInterface $collection, array $context = []):

$index = $this->isChance($this->frequency) ? mt_rand(1, $low) : mt_rand($high, $total);

return $index - 1;
return $collection->getNode($index - 1);
}

private function isChance(float $frequency): bool
Expand Down
13 changes: 7 additions & 6 deletions src/Strategy/MultipleDynamicStrategy.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@
namespace Orangesoft\Throttler\Strategy;

use Orangesoft\Throttler\Collection\CollectionInterface;
use Orangesoft\Throttler\Collection\Node;

final class MultipleDynamicStrategy implements StrategyInterface
{
/**
* @var array<string, StrategyInterface>
*/
private array $pool;
private array $pool = [];

public function __construct(StrategyInterface ...$strategies)
{
Expand All @@ -20,14 +21,14 @@ public function __construct(StrategyInterface ...$strategies)
}
}

public function getIndex(CollectionInterface $collection, array $context = []): int
public function getNode(CollectionInterface $collection, array $context = []): Node
{
if (!isset($context['strategy_name'])) {
throw new \RuntimeException('Required parameter "strategy_name" is missing.');
throw new \LogicException('Required parameter "strategy_name" is missing.');
}

if (!class_exists($context['strategy_name']) || !is_a($context['strategy_name'], StrategyInterface::class, true)) {
throw new \RuntimeException(
throw new \LogicException(
vsprintf('Strategy must be a class that exists and implements "%s" interface, "%s" given.', [
StrategyInterface::class,
$context['strategy_name'],
Expand All @@ -36,14 +37,14 @@ public function getIndex(CollectionInterface $collection, array $context = []):
}

if (!isset($this->pool[$context['strategy_name']])) {
throw new \RuntimeException(
throw new \LogicException(
sprintf('Strategy "%s" is undefined.', $context['strategy_name'])
);
}

/** @var StrategyInterface $strategy */
$strategy = $this->pool[$context['strategy_name']];

return $strategy->getIndex($collection, $context);
return $strategy->getNode($collection, $context);
}
}
9 changes: 6 additions & 3 deletions src/Strategy/RandomStrategy.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,18 @@

use Orangesoft\Throttler\Collection\CollectionInterface;
use Orangesoft\Throttler\Collection\Exception\EmptyCollectionException;
use Orangesoft\Throttler\Collection\Node;

final class RandomStrategy implements StrategyInterface
{
public function getIndex(CollectionInterface $collection, array $context = []): int
public function getNode(CollectionInterface $collection, array $context = []): Node
{
if ($collection->isEmpty()) {
throw new EmptyCollectionException('Collection of nodes must not be empty.');
throw new EmptyCollectionException();
}

return mt_rand(0, count($collection) - 1);
$index = mt_rand(0, count($collection) - 1);

return $collection->getNode($index);
}
}
9 changes: 6 additions & 3 deletions src/Strategy/RoundRobinStrategy.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Orangesoft\Throttler\Collection\CollectionInterface;
use Orangesoft\Throttler\Collection\Exception\EmptyCollectionException;
use Orangesoft\Throttler\Collection\Node;

final class RoundRobinStrategy implements StrategyInterface
{
Expand All @@ -14,12 +15,14 @@ public function __construct(
) {
}

public function getIndex(CollectionInterface $collection, array $context = []): int
public function getNode(CollectionInterface $collection, array $context = []): Node
{
if ($collection->isEmpty()) {
throw new EmptyCollectionException('Collection of nodes must not be empty.');
throw new EmptyCollectionException();
}

return $this->counter->next($context['counter_name'] ?? self::class) % count($collection);
$index = $this->counter->next($context['counter_name'] ?? self::class) % count($collection);

return $collection->getNode($index);
}
}
8 changes: 4 additions & 4 deletions src/Strategy/SmoothWeightedRoundRobinStrategy.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@ final class SmoothWeightedRoundRobinStrategy implements StrategyInterface
*/
private array $currentWeights = [];

public function getIndex(CollectionInterface $collection, array $context = []): int
public function getNode(CollectionInterface $collection, array $context = []): Node
{
if ($collection->isEmpty()) {
throw new EmptyCollectionException('Collection of nodes must not be empty.');
throw new EmptyCollectionException();
}

if (!$collection->isWeighted()) {
throw new UnweightedCollectionException('All nodes in the collection must be weighted.');
throw new UnweightedCollectionException();
}

if (0 === count($this->weights) || 0 === count($this->currentWeights)) {
Expand All @@ -41,7 +41,7 @@ public function getIndex(CollectionInterface $collection, array $context = []):

$this->recalculateCurrentWeights($maxCurrentWeightIndex);

return $maxCurrentWeightIndex;
return $collection->getNode($maxCurrentWeightIndex);
}

private function getMaxCurrentWeightIndex(): int
Expand Down
3 changes: 2 additions & 1 deletion src/Strategy/StrategyInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
namespace Orangesoft\Throttler\Strategy;

use Orangesoft\Throttler\Collection\CollectionInterface;
use Orangesoft\Throttler\Collection\Node;

interface StrategyInterface
{
public function getIndex(CollectionInterface $collection, array $context = []): int;
public function getNode(CollectionInterface $collection, array $context = []): Node;
}
10 changes: 5 additions & 5 deletions src/Strategy/WeightedRandomStrategy.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@ final class WeightedRandomStrategy implements StrategyInterface
{
private int $sumWeight = 0;

public function getIndex(CollectionInterface $collection, array $context = []): int
public function getNode(CollectionInterface $collection, array $context = []): Node
{
if ($collection->isEmpty()) {
throw new EmptyCollectionException('Collection of nodes must not be empty.');
throw new EmptyCollectionException();
}

if (!$collection->isWeighted()) {
throw new UnweightedCollectionException('All nodes in the collection must be weighted.');
throw new UnweightedCollectionException();
}

$currentWeight = 0;
Expand All @@ -31,12 +31,12 @@ public function getIndex(CollectionInterface $collection, array $context = []):

$randomWeight = mt_rand(1, $this->sumWeight);

/** @var array<int, Node> $collection*/
/** @var array<int, Node> $collection */
foreach ($collection as $index => $node) {
$currentWeight += $node->weight;

if ($randomWeight <= $currentWeight) {
return $index;
return $collection->getNode($index);
}
}

Expand Down
Loading

0 comments on commit 21dad6e

Please sign in to comment.