Passed
Push — master ( 022024...99934a )
by Vince
01:30
created

limiter   D

Complexity

Total Complexity 59

Size/Duplication

Total Lines 433
Duplicated Lines 0 %

Importance

Changes 17
Bugs 0 Features 0
Metric Value
eloc 139
c 17
b 0
f 0
dl 0
loc 433
rs 4.08
wmc 59

21 Methods

Rating   Name   Duplication   Size   Complexity  
A isUnlimited() 0 3 1
A getCapacity() 0 3 1
A getTimeframe() 0 3 1
A packerObj() 0 3 1
A getAccount() 0 10 3
A setCapacity() 0 9 4
A setTimeframe() 0 21 5
A bucketObj() 0 3 1
A __construct() 0 12 3
A getThrottle() 0 26 5
A setUnlimited() 0 13 6
A setAccount() 0 4 1
A hasOptionProperty() 0 9 4
A unpackBucket() 0 20 2
A save() 0 16 1
A getLeakRate() 0 3 1
A throttlePause() 0 17 4
A setLeakRate() 0 13 5
A throttleRequest() 0 19 4
A setupOptions() 0 19 4
A getLastAccessDate() 0 9 2

How to fix   Complexity   

Complex Class

Complex classes like limiter often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use limiter, and based on these observations, apply Extract Interface, too.

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