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

CacheCounter::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 1
eloc 3
c 1
b 0
f 1
nc 1
nop 3
dl 0
loc 5
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
        $this->limit = $limit;
29
        $this->period = $period * self::MILLISECONDS_PER_SECOND;
30
        $this->storage = $storage;
31
    }
32
33
    public function setId(string $id): void
34
    {
35
        $this->id = $id;
36
    }
37
38
    public function limitIsReached(): bool
39
    {
40
        $this->checkParams();
41
        $this->arrivalTime = $this->getArrivalTime();
42
        $theoreticalArrivalTime = $this->calculateTheoreticalArrivalTime($this->getStorageValue());
43
44
        if ($this->remainingEmpty($theoreticalArrivalTime)) {
45
            return true;
46
        }
47
48
        $this->setStorageValue($theoreticalArrivalTime);
49
50
        return false;
51
    }
52
53
    private function checkParams(): void
54
    {
55
        if ($this->id === null) {
56
            throw new \RuntimeException('The counter id not set');
57
        }
58
59
        if ($this->limit < 1) {
60
            throw new \InvalidArgumentException('The limit must be a positive value.');
61
        }
62
63
        if ($this->period < 1) {
64
            throw new \InvalidArgumentException('The period must be a positive value.');
65
        }
66
    }
67
68
    private function getEmissionInterval(): float
69
    {
70
        return (float)($this->period / $this->limit);
71
    }
72
73
    private function calculateTheoreticalArrivalTime(float $theoreticalArrivalTime): float
74
    {
75
        return max($this->arrivalTime, $theoreticalArrivalTime) + $this->getEmissionInterval();
76
    }
77
78
    private function remainingEmpty(float $theoreticalArrivalTime): bool
79
    {
80
        $allowAt = $theoreticalArrivalTime - $this->period;
81
82
        return ((floor($this->arrivalTime - $allowAt) / $this->getEmissionInterval()) + 0.5) < 1;
83
    }
84
85
    private function getStorageValue(): float
86
    {
87
        return $this->storage->get($this->id, $this->arrivalTime);
88
    }
89
90
    private function setStorageValue(float $theoreticalArrivalTime): void
91
    {
92
        $this->storage->set($this->id, $theoreticalArrivalTime);
93
    }
94
95
    private function getArrivalTime(): int
96
    {
97
        return time() * self::MILLISECONDS_PER_SECOND;
98
    }
99
}
100