Completed
Pull Request — master (#25)
by Julián
04:08 queued 03:07
created

ApcuRateLimiter::getCurrent()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 1
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 5
    public function __construct(string $keyPrefix = '')
16
    {
17 5
        if (!\extension_loaded('apcu') || \ini_get('apc.enabled') === '0') {
18
            throw new CannotUseRateLimiter('APCu extension is not loaded or not enabled.');
19
        }
20
21 5
        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 5
        $this->keyPrefix = $keyPrefix;
26 5
    }
27
28 2
    public function limit(string $identifier, Rate $rate): void
29
    {
30 2
        $interval = $rate->getInterval();
31 2
        $valueKey = $this->valueKey($identifier, $interval);
32 2
        $timeKey = $this->timeKey($identifier, $interval);
33
34 2
        $current = $this->getCurrent($valueKey);
35 2
        if ($current >= $rate->getOperations()) {
36 2
            throw LimitExceeded::for($identifier, $rate);
37
        }
38
39 2
        $this->updateCounter($valueKey, $timeKey, $rate->getInterval());
40 2
    }
41
42 3
    public function limitSilently(string $identifier, Rate $rate): Status
43
    {
44 3
        $interval = $rate->getInterval();
45 3
        $valueKey = $this->valueKey($identifier, $interval);
46 3
        $timeKey = $this->timeKey($identifier, $interval);
47
48 3
        $current = $this->getCurrent($valueKey);
49 3
        if ($current <= $rate->getOperations()) {
50 3
            $current = $this->updateCounter($valueKey, $timeKey, $interval);
51
        }
52
53 3
        return Status::from(
54 3
            \sprintf('%s%s', $this->keyPrefix, $identifier),
55 3
            $current,
56 3
            $rate->getOperations(),
57 3
            \time() + \max(0, $interval - $this->getElapsedTime($timeKey))
58
        );
59
    }
60
61 5
    private function valueKey(string $identifier, int $interval): string
62
    {
63 5
        return "{$this->keyPrefix}{$identifier}:value:$interval";
64
    }
65
66 5
    private function timeKey(string $identifier, int $interval): string
67
    {
68 5
        return "{$this->keyPrefix}{$identifier}:time:$interval";
69
    }
70
71 5
    private function getCurrent(string $valueKey): int
72
    {
73 5
        return (int) \apcu_fetch($valueKey);
74
    }
75
76 5
    private function updateCounter(string $valueKey, string $timeKey, int $interval): int
77
    {
78 5
        $current = \apcu_inc($valueKey, 1, $success, $interval);
79
80 5
        if ($current === 1) {
81 5
            \apcu_store($timeKey, \time(), $interval);
82
        }
83
84 5
        return $current === false ? 1 : $current;
85
    }
86
87 3
    protected function getElapsedTime(string $timeKey): int
88
    {
89 3
        return \time() - (int) \apcu_fetch($timeKey);
90
    }
91
}
92