Completed
Pull Request — master (#28)
by Julián
04:48
created

MemcachedRateLimiter::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2.0185

Importance

Changes 0
Metric Value
dl 0
loc 10
ccs 5
cts 6
cp 0.8333
rs 9.9332
c 0
b 0
f 0
cc 2
nc 2
nop 2
crap 2.0185
1
<?php
2
3
declare(strict_types=1);
4
5
namespace RateLimit;
6
7
use Memcached;
8
use RateLimit\Exception\CannotUseRateLimiter;
9
use RateLimit\Exception\LimitExceeded;
10
11
final class MemcachedRateLimiter implements RateLimiter, SilentRateLimiter
12
{
13
    private const MEMCACHED_SECONDS_LIMIT = 2592000; // Number of seconds in 30 days
14
15
    /** @var Memcached */
16
    private $memcached;
17
18
    /** @var string */
19
    private $keyPrefix;
20
21 5
    public function __construct(Memcached $memcached, string $keyPrefix = '')
22
    {
23
        // @see https://www.php.net/manual/en/memcached.increment.php#111187
24 5
        if ($memcached->getOption(Memcached::OPT_BINARY_PROTOCOL) !== 1) {
25
            throw new CannotUseRateLimiter('Memcached "OPT_BINARY_PROTOCOL" option should be set to "true".');
26
        }
27
28 5
        $this->memcached = $memcached;
29 5
        $this->keyPrefix = $keyPrefix;
30 5
    }
31
32 2
    public function limit(string $identifier, Rate $rate): void
33
    {
34 2
        $limitKey = $this->limitKey($identifier, $rate->getInterval());
35
36 2
        $current = $this->getCurrent($limitKey);
37 2
        if ($current >= $rate->getOperations()) {
38 2
            throw LimitExceeded::for($identifier, $rate);
39
        }
40
41 2
        $this->updateCounter($limitKey, $rate->getInterval());
42 2
    }
43
44 3
    public function limitSilently(string $identifier, Rate $rate): Status
45
    {
46 3
        $interval = $rate->getInterval();
47 3
        $limitKey = $this->limitKey($identifier, $interval);
48 3
        $timeKey = $this->timeKey($identifier, $interval);
49
50 3
        $current = $this->getCurrent($limitKey);
51 3
        if ($current <= $rate->getOperations()) {
52 3
            $current = $this->updateCounterAndTime($limitKey, $timeKey, $interval);
53
        }
54
55 3
        return Status::from(
56 3
            $identifier,
57 3
            $current,
58 3
            $rate->getOperations(),
59 3
            \time() + \max(0, $interval - $this->getElapsedTime($timeKey))
60
        );
61
    }
62
63 5
    private function limitKey(string $identifier, int $interval): string
64
    {
65 5
        return \sprintf('%s%s:%d', $this->keyPrefix, $identifier, $interval);
66
    }
67
68 3
    private function timeKey(string $identifier, int $interval): string
69
    {
70 3
        return \sprintf('%s%s:%d:time', $this->keyPrefix, $identifier, $interval);
71
    }
72
73 5
    private function getCurrent(string $limitKey): int
74
    {
75 5
        return (int) $this->memcached->get($limitKey);
76
    }
77
78 3
    private function updateCounterAndTime(string $limitKey, string $timeKey, int $interval): int
79
    {
80 3
        $current = $this->updateCounter($limitKey, $interval);
81
82 3
        if ($current === 1) {
83 3
            $this->memcached->add($timeKey, \time(), $this->intervalToMemcachedTime($interval));
84
        }
85
86 3
        return $current;
87
    }
88
89 5
    private function updateCounter(string $limitKey, int $interval): int
90
    {
91 5
        $current = $this->memcached->increment($limitKey, 1, 1, $this->intervalToMemcachedTime($interval));
92
93 5
        return $current === false ? 1 : $current;
94
    }
95
96 3
    private function getElapsedTime(string $timeKey): int
97
    {
98 3
        return \time() - (int) $this->memcached->get($timeKey);
99
    }
100
101
    /**
102
     * Interval to Memcached expiration time.
103
     *
104
     * @see https://www.php.net/manual/en/memcached.expiration.php
105
     *
106
     * @param int $interval
107
     *
108
     * @return int
109
     */
110 5
    private function intervalToMemcachedTime(int $interval): int
111
    {
112 5
        return $interval <= self::MEMCACHED_SECONDS_LIMIT ? $interval : \time() + $interval;
113
    }
114
}
115