Completed
Push — master ( 8ef4ef...e0b17e )
by Marcel
06:55
created

FixedWindowLimiter::getCreationDate()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 12
rs 9.8666
c 0
b 0
f 0
cc 2
nc 2
nop 1
1
<?php
2
3
namespace BeyondCode\FixedWindowLimiter;
4
5
use Carbon\Carbon;
6
use Carbon\CarbonInterval;
7
use Illuminate\Support\Facades\Redis;
8
use Illuminate\Redis\Connections\Connection;
9
10
class FixedWindowLimiter
11
{
12
    /** @var int */
13
    private $timeWindow;
14
15
    /** @var int */
16
    private $limit;
17
18
    public function __construct(CarbonInterval $timeWindow, int $limit)
19
    {
20
        $this->timeWindow = $timeWindow->totalSeconds;
0 ignored issues
show
Documentation Bug introduced by
The property $timeWindow was declared of type integer, but $timeWindow->totalSeconds is of type double. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
21
22
        $this->limit = $limit;
23
    }
24
25
    public static function create(CarbonInterval $timeWindow, int $limit): self
26
    {
27
        return new static($timeWindow, $limit);
28
    }
29
    
30
    public function attempt(string $resource): bool
31
    {
32
        $key = $this->buildKey($resource);
33
34
        $hits = $this->getConnection()->hincrby($key, 'attempts', 1);
35
36
        if ($hits === 1) {
37
            $this->getConnection()->hset($key, 'created_at', now()->toDateTimeString());
38
            $this->getConnection()->expire($key, $this->timeWindow);
39
        }
40
41
        return $hits <= $this->limit;
42
    }
43
44
    public function getUsage(string $resource): int
45
    {
46
        $usage = (int)$this->getConnection()->hget($this->buildKey($resource), 'attempts');
47
48
        return min($this->limit, $usage);
49
    }
50
51
    public function getRemaining(string $resource): int
52
    {
53
        $remaining = $this->limit - $this->getUsage($resource);
54
55
        return max(0, $remaining);
56
    }
57
58
    private function getConnection(): Connection
59
    {
60
        return Redis::connection(config('limiter.connection'));
61
    }
62
63
    private function buildKey(string $resource): string
64
    {
65
        return config('limiter.prefix') . $resource;
66
    }
67
68
    public function getRealUsage(string $resource): int
69
    {
70
        return (int)$this->getConnection()->hget($this->buildKey($resource), 'attempts');
71
    }
72
73
    public function reset(string $resource, array $additionalData = [])
74
    {
75
        $key = $this->buildKey($resource);
76
77
        $this->getConnection()->hset($key, 'attempts', 0);
78
        $this->getConnection()->hset($key, 'created_at', now()->toDateTimeString());
79
80
        foreach ($additionalData as $field => $value) {
81
            $this->getConnection()->hset($key, $field, $value);
82
        }
83
84
        $this->getConnection()->expire($key, $this->timeWindow);
85
    }
86
87
    public function getCreationDate(string $resource): ?Carbon
88
    {
89
        $key = $this->buildKey($resource);
90
91
        $creationDate = $this->getConnection()->hget($key, 'created_at');
92
93
        if (is_null($creationDate)) {
94
            return null;
95
        }
96
97
        return new Carbon($creationDate);
98
    }
99
100
    public function getAdditionalData(string $resource, string $key)
101
    {
102
        return $this->getConnection()->hget($this->buildKey($resource), $key);
103
    }
104
}
105