Passed
Pull Request — master (#204)
by
unknown
02:24
created

CacheCounter::setStorageValue()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 1 Features 1
Metric Value
cc 1
eloc 1
c 2
b 1
f 1
nc 1
nop 1
dl 0
loc 3
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Yii\Web\RateLimiter;
6
7
use Psr\SimpleCache\CacheInterface;
8
9
/**
10
 * CacheCounter implements generic сell rate limit algorithm https://en.wikipedia.org/wiki/Generic_cell_rate_algorithm
11
 */
12
final class CacheCounter
13
{
14
    private const MILLISECONDS_PER_SECOND = 1000;
15
16
    private int $period;
17
18
    private int $limit;
19
20
    private ?string $id = null;
21
22
    private CacheInterface $storage;
23
24
    private int $arrivalTime;
25
26
    public function __construct(int $limit, int $period, CacheInterface $storage)
27
    {
28
        if ($limit < 1) {
29
            throw new \InvalidArgumentException('The limit must be a positive value.');
30
        }
31
32
        if ($period < 1) {
33
            throw new \InvalidArgumentException('The period must be a positive value.');
34
        }
35
36
        $this->limit = $limit;
37
        $this->period = $period * self::MILLISECONDS_PER_SECOND;
38
        $this->storage = $storage;
39
    }
40
41
    public function setId(string $id): void
42
    {
43
        $this->id = $id;
44
    }
45
46
    public function limitIsReached(): bool
47
    {
48
        if ($this->id === null) {
49
            throw new \RuntimeException('The counter id not set');
50
        }
51
52
        $this->arrivalTime = $this->getArrivalTime();
53
        $theoreticalArrivalTime = $this->calculateTheoreticalArrivalTime($this->getStorageValue());
54
55
        if ($this->remainingEmpty($theoreticalArrivalTime)) {
56
            return true;
57
        }
58
59
        $this->setStorageValue($theoreticalArrivalTime);
60
61
        return false;
62
    }
63
64
    private function getEmissionInterval(): float
65
    {
66
        return (float)($this->period / $this->limit);
67
    }
68
69
    private function calculateTheoreticalArrivalTime(float $theoreticalArrivalTime): float
70
    {
71
        return max($this->arrivalTime, $theoreticalArrivalTime) + $this->getEmissionInterval();
72
    }
73
74
    private function remainingEmpty(float $theoreticalArrivalTime): bool
75
    {
76
        $allowAt = $theoreticalArrivalTime - $this->period;
77
78
        return ((floor($this->arrivalTime - $allowAt) / $this->getEmissionInterval()) + 0.5) < 1;
79
    }
80
81
    private function getStorageValue(): float
82
    {
83
        return $this->storage->get($this->id, (float)$this->arrivalTime);
84
    }
85
86
    private function setStorageValue(float $theoreticalArrivalTime): void
87
    {
88
        $this->storage->set($this->id, $theoreticalArrivalTime);
89
    }
90
91
    private function getArrivalTime(): int
92
    {
93
        return time() * self::MILLISECONDS_PER_SECOND;
94
    }
95
}
96