Completed
Push — master ( ec7be7...8d0fea )
by Freek
17s queued 12s
created

RateLimited::releaseAfterBackoff()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 10
rs 9.9332
c 0
b 0
f 0
cc 2
nc 2
nop 2
1
<?php
2
3
namespace Spatie\RateLimitedMiddleware;
4
5
use Closure;
6
use Illuminate\Support\Facades\Redis;
7
8
class RateLimited
9
{
10
    /** @var bool|\Closure */
11
    protected $enabled = true;
12
13
    /** @var string */
14
    protected $connectionName = '';
15
16
    /** @var string */
17
    protected $key;
18
19
    /** @var int */
20
    protected $timeSpanInSeconds = 1;
21
22
    /** @var int */
23
    protected $allowedNumberOfJobsInTimeSpan = 5;
24
25
    /** @var int */
26
    protected $releaseInSeconds = 5;
27
28
    /** @var array */
29
    protected $releaseRandomSeconds = null;
30
31
    public function __construct()
32
    {
33
        $calledByClass = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2)[1]['class'];
34
35
        $this->key($calledByClass);
36
    }
37
38
    /**
39
     * @param bool|\Closure $enabled
40
     *
41
     * @return $this
42
     */
43
    public function enabled($enabled = true)
44
    {
45
        $this->enabled = $enabled;
46
47
        return $this;
48
    }
49
50
    public function connectionName(string $connectionName)
51
    {
52
        $this->connectionName = $connectionName;
53
54
        return $this;
55
    }
56
57
    public function key(string $key)
58
    {
59
        $this->key = $key;
60
61
        return $this;
62
    }
63
64
    public function timespanInSeconds(int $timespanInSeconds)
65
    {
66
        $this->timeSpanInSeconds = $timespanInSeconds;
67
68
        return $this;
69
    }
70
71
    public function allow(int $allowedNumberOfJobsInTimeSpan)
72
    {
73
        $this->allowedNumberOfJobsInTimeSpan = $allowedNumberOfJobsInTimeSpan;
74
75
        return $this;
76
    }
77
78
    public function everySecond(int $timespanInSeconds = 1)
79
    {
80
        $this->timeSpanInSeconds = $timespanInSeconds;
81
82
        return $this;
83
    }
84
85
    public function everySeconds(int $timespanInSeconds)
86
    {
87
        return $this->everySecond($timespanInSeconds);
88
    }
89
90
    public function everyMinute(int $timespanInMinutes = 1)
91
    {
92
        return $this->everySecond($timespanInMinutes * 60);
93
    }
94
95
    public function everyMinutes(int $timespanInMinutes)
96
    {
97
        return $this->everySecond($timespanInMinutes * 60);
98
    }
99
100
    public function releaseAfterOneSecond()
101
    {
102
        return $this->releaseAfterSeconds(1);
103
    }
104
105
    public function releaseAfterSeconds(int $releaseInSeconds)
106
    {
107
        $this->releaseInSeconds = $releaseInSeconds;
108
109
        return $this;
110
    }
111
112
    public function releaseAfterOneMinute()
113
    {
114
        return $this->releaseAfterMinutes(1);
115
    }
116
117
    public function releaseAfterMinutes(int $releaseInMinutes)
118
    {
119
        return $this->releaseAfterSeconds($releaseInMinutes * 60);
120
    }
121
122
    public function releaseAfterRandomSeconds(int $min = 1, int $max = 10)
123
    {
124
        $this->releaseRandomSeconds = [$min, $max];
125
126
        return $this;
127
    }
128
129
    public function releaseAfterBackoff(int $attemptedCount, int $backoffRate = 2)
130
    {
131
        $releaseAfterSeconds = 0;
132
        $interval = $this->releaseInSeconds;
133
        for($attempt = 0; $attempt <= $attemptedCount; $attempt++) {
134
            $releaseAfterSeconds += $interval * pow($backoffRate, $attempt);
135
        }
136
137
        return $this->releaseAfterSeconds($releaseAfterSeconds);
138
    }
139
140
    protected function releaseDuration(): int
141
    {
142
        if (! is_null($this->releaseRandomSeconds)) {
143
            return random_int(...$this->releaseRandomSeconds);
144
        }
145
146
        return $this->releaseInSeconds;
147
    }
148
149
    public function handle($job, $next)
150
    {
151
        if ($this->enabled instanceof Closure) {
152
            $this->enabled = (bool) $this->enabled();
153
        }
154
155
        if (! $this->enabled) {
156
            return $next($job);
157
        }
158
159
        Redis::connection($this->connectionName)
160
            ->throttle($this->key)
161
            ->block(0)
162
            ->allow($this->allowedNumberOfJobsInTimeSpan)
163
            ->every($this->timeSpanInSeconds)
164
            ->then(function () use ($job, $next) {
165
                $next($job);
166
            }, function () use ($job) {
167
                $job->release($this->releaseDuration());
168
            });
169
    }
170
}
171