Completed
Pull Request — master (#25)
by Julián
09:32
created

ApcuRateLimiter::timeKey()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace RateLimit;
6
7
use RateLimit\Exception\CannotUseRateLimiter;
8
use RateLimit\Exception\LimitExceeded;
9
10
final class ApcuRateLimiter implements RateLimiter, SilentRateLimiter
11
{
12
    /** @var string */
13
    private $keyPrefix;
14
15
    public function __construct(string $keyPrefix = '')
16
    {
17
        if (!\extension_loaded('apcu') || \ini_get('apc.enabled') === '0') {
18
            throw new CannotUseRateLimiter('APCu extension is not loaded or not enabled.');
19
        }
20
21
        if (\ini_get('apc.use_request_time') === '1') {
22
            throw new CannotUseRateLimiter('APCu ini configuration "apc.use_request_time" should be set to "0".');
23
        }
24
25
        $this->keyPrefix = $keyPrefix;
26
    }
27
28
    public function limit(string $identifier, Rate $rate): void
29
    {
30
        $interval = $rate->getInterval();
31
        $valueKey = $this->valueKey($identifier, $interval);
32
        $timeKey = $this->timeKey($identifier, $interval);
33
34
        $current = $this->getCurrent($valueKey);
35
        if ($current >= $rate->getOperations()) {
36
            throw LimitExceeded::for($identifier, $rate);
37
        }
38
39
        $this->updateCounter($valueKey, $timeKey, $rate->getInterval());
40
    }
41
42
    public function limitSilently(string $identifier, Rate $rate): Status
43
    {
44
        $interval = $rate->getInterval();
45
        $valueKey = $this->valueKey($identifier, $interval);
46
        $timeKey = $this->timeKey($identifier, $interval);
47
48
        $current = $this->getCurrent($valueKey);
49
        if ($current <= $rate->getOperations()) {
50
            $current = $this->updateCounter($valueKey, $timeKey, $interval);
51
        }
52
53
        return Status::from(
54
            \sprintf('%s%s', $this->keyPrefix, $identifier),
55
            $current,
56
            $rate->getOperations(),
57
            \time() + \max(0, $interval - $this->getElapsedTime($timeKey))
58
        );
59
    }
60
61
    private function valueKey(string $identifier, int $interval): string
62
    {
63
        return "{$this->keyPrefix}{$identifier}:value:$interval";
64
    }
65
66
    private function timeKey(string $identifier, int $interval): string
67
    {
68
        return "{$this->keyPrefix}{$identifier}:time:$interval";
69
    }
70
71
    private function getCurrent(string $valueKey): int
72
    {
73
        return (int) \apcu_fetch($valueKey);
74
    }
75
76
    private function updateCounter(string $valueKey, string $timeKey, int $interval): int
77
    {
78
        $current = \apcu_inc($valueKey, 1, $success, $interval);
79
80
        if ($current === 1) {
81
            \apcu_store($timeKey, \time(), $interval);
82
        }
83
84
        return $current === false ? 1 : $current;
85
    }
86
87
    protected function getElapsedTime(string $timeKey): int
88
    {
89
        return \time() - (int) \apcu_fetch($timeKey);
90
    }
91
}
92