Passed
Push — master ( a526b8...71491a )
by Vince
01:39
created

limiter::setupOptions()   B

Complexity

Conditions 9
Paths 28

Size

Total Lines 26
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
cc 9
eloc 13
c 4
b 0
f 0
nc 28
nop 0
dl 0
loc 26
rs 8.0555
1
<?php
2
/**
3
 * ==================================
4
 * Responsible PHP API
5
 * ==================================
6
 *
7
 * @link Git https://github.com/vince-scarpa/responsibleAPI.git
8
 *
9
 * @api Responible API
10
 * @package responsible\core\throttle
11
 *
12
 * @author Vince scarpa <[email protected]>
13
 *
14
 */
15
namespace responsible\core\throttle;
16
17
use responsible\core\exception;
18
use responsible\core\throttle;
19
use responsible\core\user;
20
21
class limiter
22
{
23
    /**
24
     * [$capacity Bucket volume]
25
     * @var integer
26
     */
27
    private $capacity = 100;
28
29
    /**
30
     * [$leakRate Constant rate at which the bucket will leak]
31
     * @var string|integer
32
     */
33
    private $leakRate = 1;
34
35
    /**
36
     * [$unpacked]
37
     */
38
    private $unpacked;
39
40
    /**
41
     * [$packed]
42
     * @var string
43
     */
44
    private $packed;
45
46
    /**
47
     * [$bucket]
48
     */
49
    private $bucket;
50
51
    /**
52
     * [$account User account object]
53
     */
54
    private $account;
55
56
    /**
57
     * [$options Responisble options]
58
     */
59
    private $options;
60
61
    /**
62
     * [$timeframe Durations are in seconds]
63
     * @var array
64
     */
65
    private static $timeframe = [
66
        'SECOND' => 1,
67
        'MINUTE' => 60,
68
        'HOUR' => 3600,
69
        'DAY' => 86400,
70
        'CUSTOM' => 0,
71
    ];
72
73
    /**
74
     * [$window Timeframe window]
75
     * @var integer|string
76
     */
77
    private $window = 'MINUTE';
78
79
    /**
80
     * [$unlimited Rate limiter bypass if true]
81
     * @var boolean
82
     */
83
    private $unlimited = false;
84
85
    /**
86
     * [$scope Set the default scope]
87
     * @var string
88
     */
89
    private $scope = 'private';
90
91
    /**
92
     * [setupOptions Set any Responsible API options]
93
     * @return self
94
     */
95
    public function setupOptions()
96
    {
97
        $options = $this->getOptions();
98
99
        $this->setCapacity($options);
100
101
        $this->setTimeframe($options);
102
103
        $this->setLeakRate($options);
104
105
        $unlimited = 
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: $unlimited = (IssetNode ...uestType'] === 'debug'), Probably Intended Meaning: $unlimited = IssetNode &...uestType'] === 'debug')
Loading history...
106
            (isset($options['unlimited']) && ($options['unlimited'] == 1 || $options['unlimited'] == true)) || 
107
            (isset($options['requestType']) && $options['requestType'] === 'debug')
108
        ;
109
110
        if ($unlimited) {
111
            $this->setUnlimited();
112
        }
113
114
        if( isset($this->account->scope) &&
115
            ($this->account->scope == 'anonymous' || $this->account->scope == 'public')
116
        ) {
117
           $this->scope = $this->account->scope;
118
        }
119
120
        return $this;
121
    }
122
123
    /**
124
     * [throttleRequest Build the Responsible API throttle]
125
     * @return boolean|void
126
     */
127
    public function throttleRequest()
128
    {
129
        if ($this->isUnlimited() || $this->scope !== 'private') {
130
            return true;
131
        }
132
133
        $account = $this->getAccount();
134
135
        if (empty($account)) {
136
            return false;
137
        }
138
139
        /**
140
         * [$unpack Unpack the account bucket data]
141
         */
142
        $this->unpacked = (new throttle\tokenPack)->unpack(
143
            $account->bucket
144
        );
145
        if (empty($this->unpacked)) {
146
            $this->unpacked = array(
147
                'drops' => 1,
148
                'time' => $account->access,
149
            );
150
        }
151
152
        $this->bucket = (new throttle\tokenBucket())
153
            ->setTimeframe($this->getTimeframe())
154
            ->setCapacity($this->getCapacity())
155
            ->setLeakRate($this->getLeakRate())
156
            ->pour($this->unpacked['drops'], $this->unpacked['time'])
157
        ;
158
159
        /**
160
         * Check if the bucket still has capacity to fill
161
         */
162
        if ($this->bucket->capacity()) {
163
            $this->bucket->pause(false);
164
            $this->bucket->fill();
165
        } else {
166
            if ($this->getLeakRate() <= 0) {
167
                if ($this->unpacked['pauseAccess'] == false) {
168
                    $this->bucket->pause(true);
169
                    $this->save();
170
                }
171
172
                if ($this->bucket->refill($account->access)) {
173
                    $this->save();
174
                }
175
            }
176
177
            (new exception\errorException)->error('TOO_MANY_REQUESTS');
178
        }
179
180
        $this->save();
181
    }
182
183
    /**
184
     * [updateBucket Store the buckets token data and user access time]
185
     * @return void
186
     */
187
    private function save()
188
    {
189
        $this->packed = (new throttle\tokenPack)->pack(
190
            $this->bucket->getTokenData()
191
        );
192
193
        /**
194
         * [Update account access]
195
         */
196
        (new user\user)
197
            ->setAccountID($this->getAccount()->account_id)
198
            ->setBucketToken($this->packed)
199
            ->updateAccountAccess()
200
        ;
201
    }
202
203
    /**
204
     * [getThrottle Return a list of the throttled results]
205
     * @return array
206
     */
207
    public function getThrottle()
208
    {
209
        if ($this->isUnlimited() || $this->scope !== 'private') {
210
            return array(
211
                'unlimited' => true,
212
            );
213
        }
214
215
        $windowFrame = (is_string($this->getTimeframe()))
216
        ? $this->getTimeframe()
217
        : $this->getTimeframe() . 'secs'
218
        ;
219
220
        if (is_null($this->bucket)) {
221
            return;
222
        }
223
224
        return array(
225
            'limit' => $this->getCapacity(),
226
            'leakRate' => $this->getLeakRate(),
227
            'leak' => $this->bucket->getLeakage(),
228
            'lastAccess' => $this->getLastAccessDate(),
229
            'description' => $this->getCapacity() . ' requests per ' . $windowFrame,
230
            'bucket' => $this->bucket->getTokenData(),
231
        );
232
    }
233
234
    /**
235
     * [getLastAccessDate Get the last recorded access in date format]
236
     * @return string
237
     */
238
    private function getLastAccessDate()
239
    {
240
        if (isset($this->bucket->getTokenData()['time'])) {
241
            return date('m/d/y h:i:sa', $this->bucket->getTokenData()['time']);
242
        }
243
244
        return 'Can\'t be converted';
245
    }
246
247
    /**
248
     * [setAccount Set the requests account]
249
     * @return self
250
     */
251
    public function setAccount($account)
252
    {
253
        $this->account = $account;
254
        return $this;
255
    }
256
257
    /**
258
     * [getAccount Get the requests account]
259
     */
260
    public function getAccount()
261
    {
262
        if(is_null($this->account)) {
263
            (new exception\errorException)
264
                ->setOptions($this->getOptions())
265
                ->error('UNAUTHORIZED');
266
            return;
267
        }
268
269
        return $this->account;
270
    }
271
272
    /**
273
     * [options Responsible API options]
274
     * @param array $options
275
     */
276
    public function options($options)
277
    {
278
        $this->options = $options;
279
        return $this;
280
    }
281
282
    /**
283
     * [getOptions Get the stored Responsible API options]
284
     * @return array
285
     */
286
    private function getOptions()
287
    {
288
        return $this->options;
289
    }
290
291
    /**
292
     * [hasOptionProperty Check if an option property is set]
293
     * @param  array  $options
294
     * @param  string  $property
295
     * @return boolean
296
     */
297
    private function hasOptionProperty(array $options, $property, $default = false)
298
    {
299
        $val = isset($options[$property]) ? $options[$property] : $default;
300
301
        if ($val && empty($options[$property])) {
302
            $val = $default;
303
        }
304
305
        return $val;
306
    }
307
308
    /**
309
     * [setCapacity Set the buckets capacity]
310
     * @param array $options
311
     */
312
    public function setCapacity($options)
313
    {
314
        $hasCapacityOption = $this->hasOptionProperty($options, 'rateLimit', false);
315
316
        if ($hasCapacityOption) {
317
            if (!is_integer($hasCapacityOption)||empty($hasCapacityOption)) {
0 ignored issues
show
introduced by
The condition is_integer($hasCapacityOption) is always false.
Loading history...
318
                $hasCapacityOption = false;
319
            }
320
        }
321
322
        $this->capacity = ($hasCapacityOption) ? $hasCapacityOption : $this->capacity;
0 ignored issues
show
introduced by
The condition $hasCapacityOption is always false.
Loading history...
323
    }
324
325
    /**
326
     * [getCapacity Get the buckets capacity]
327
     * @return integer
328
     */
329
    public function getCapacity()
330
    {
331
        return $this->capacity;
332
    }
333
334
    /**
335
     * [setTimeframe Set the window timeframe]
336
     * @param array $options
337
     */
338
    public function setTimeframe($options)
339
    {
340
        $timeframe = $this->hasOptionProperty($options, 'rateWindow', false);
341
342
        if (!is_string($timeframe) && !is_numeric($timeframe)) {
0 ignored issues
show
introduced by
The condition is_string($timeframe) is always false.
Loading history...
introduced by
The condition is_numeric($timeframe) is always false.
Loading history...
343
            $timeframe = $this->window;
344
        }
345
346
        if (!$timeframe) {
347
            $timeframe = $this->window;
348
        }
349
350
        if (is_numeric($timeframe)) {
351
            self::$timeframe['CUSTOM'] = $timeframe;
352
            $this->window = self::$timeframe['CUSTOM'];
353
            return;
354
        }
355
356
        if (isset(self::$timeframe[$timeframe])) {
357
            $this->window = self::$timeframe[$timeframe];
358
            return;
359
        }
360
361
        $this->window = self::$timeframe['MINUTE'];
362
    }
363
364
    /**
365
     * [getTimeframe Get the timeframe window]
366
     * @return integer|string
367
     */
368
    public function getTimeframe()
369
    {
370
        return $this->window;
371
    }
372
373
    /**
374
     * [setLeakRate Set the buckets leak rate]
375
     * Options: slow, medium, normal, default, fast or custom positive integer
376
     * @param array $options
377
     */
378
    private function setLeakRate($options)
379
    {
380
        if (isset($options['leak']) && !$options['leak']) {
381
            $options['leakRate'] = 'default';
382
        }
383
384
        $leakRate = $this->hasOptionProperty($options, 'leakRate', false);
385
386
        if (empty($leakRate) || !is_string($leakRate)) {
0 ignored issues
show
introduced by
The condition is_string($leakRate) is always false.
Loading history...
387
            $leakRate = 'default';
388
        }
389
390
        $this->leakRate = $leakRate;
391
    }
392
393
    /**
394
     * [getLeakRate Get the buckets leak rate]
395
     * @return string|integer
396
     */
397
    private function getLeakRate()
398
    {
399
        return $this->leakRate;
400
    }
401
402
    /**
403
     * [setUnlimited Rate limiter bypass]
404
     */
405
    private function setUnlimited()
406
    {
407
        $this->unlimited = true;
408
    }
409
410
    /**
411
     * [isUnlimited Check if the Responsible API is set to unlimited]
412
     * @return boolean
413
     */
414
    private function isUnlimited()
415
    {
416
        return $this->unlimited;
417
    }
418
}
419