Passed
Push — master ( a35214...e794f9 )
by Vince
01:30
created

limiter::setupOptions()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 19
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 6
Bugs 0 Features 0
Metric Value
cc 4
eloc 9
c 6
b 0
f 0
nc 2
nop 0
dl 0
loc 19
rs 9.9666
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
        $this->setUnlimited($options);
106
107
        if( isset($this->account->scope) &&
108
            ($this->account->scope == 'anonymous' || $this->account->scope == 'public')
109
        ) {
110
           $this->scope = $this->account->scope;
111
        }
112
113
        return $this;
114
    }
115
116
    /**
117
     * [throttleRequest Build the Responsible API throttle]
118
     * @return boolean|void
119
     */
120
    public function throttleRequest()
121
    {
122
        if ($this->isUnlimited() || $this->scope !== 'private') {
123
            return true;
124
        }
125
126
        $account = $this->getAccount();
127
128
        if (empty($account)) {
129
            return false;
130
        }
131
132
        /**
133
         * [$unpack Unpack the account bucket data]
134
         */
135
        $this->unpacked = (new throttle\tokenPack)->unpack(
136
            $account->bucket
137
        );
138
        if (empty($this->unpacked)) {
139
            $this->unpacked = array(
140
                'drops' => 1,
141
                'time' => $account->access,
142
            );
143
        }
144
145
        $this->bucket = (new throttle\tokenBucket())
146
            ->setTimeframe($this->getTimeframe())
147
            ->setCapacity($this->getCapacity())
148
            ->setLeakRate($this->getLeakRate())
149
            ->pour($this->unpacked['drops'], $this->unpacked['time'])
150
        ;
151
152
        /**
153
         * Check if the bucket still has capacity to fill
154
         */
155
        if ($this->bucket->capacity()) {
156
            $this->bucket->pause(false);
157
            $this->bucket->fill();
158
        } else {
159
            if ($this->getLeakRate() <= 0) {
160
                if ($this->unpacked['pauseAccess'] == false) {
161
                    $this->bucket->pause(true);
162
                    $this->save();
163
                }
164
165
                if ($this->bucket->refill($account->access)) {
166
                    $this->save();
167
                }
168
            }
169
170
            (new exception\errorException)->error('TOO_MANY_REQUESTS');
171
        }
172
173
        $this->save();
174
    }
175
176
    /**
177
     * [updateBucket Store the buckets token data and user access time]
178
     * @return void
179
     */
180
    private function save()
181
    {
182
        $this->packed = (new throttle\tokenPack)->pack(
183
            $this->bucket->getTokenData()
184
        );
185
186
        /**
187
         * [Update account access]
188
         */
189
        (new user\user)
190
            ->setAccountID($this->getAccount()->account_id)
191
            ->setBucketToken($this->packed)
192
            ->updateAccountAccess()
193
        ;
194
    }
195
196
    /**
197
     * [getThrottle Return a list of the throttled results]
198
     * @return array
199
     */
200
    public function getThrottle()
201
    {
202
        if ($this->isUnlimited() || $this->scope !== 'private') {
203
            return array(
204
                'unlimited' => true,
205
            );
206
        }
207
208
        $windowFrame = (is_string($this->getTimeframe()))
209
        ? $this->getTimeframe()
210
        : $this->getTimeframe() . 'secs'
211
        ;
212
213
        if (is_null($this->bucket)) {
214
            return;
215
        }
216
217
        return array(
218
            'limit' => $this->getCapacity(),
219
            'leakRate' => $this->getLeakRate(),
220
            'leak' => $this->bucket->getLeakage(),
221
            'lastAccess' => $this->getLastAccessDate(),
222
            'description' => $this->getCapacity() . ' requests per ' . $windowFrame,
223
            'bucket' => $this->bucket->getTokenData(),
224
        );
225
    }
226
227
    /**
228
     * [getLastAccessDate Get the last recorded access in date format]
229
     * @return string
230
     */
231
    private function getLastAccessDate()
232
    {
233
        if (isset($this->bucket->getTokenData()['time'])) {
234
            return date('m/d/y h:i:sa', $this->bucket->getTokenData()['time']);
235
        }
236
237
        return 'Can\'t be converted';
238
    }
239
240
    /**
241
     * [setAccount Set the requests account]
242
     * @return self
243
     */
244
    public function setAccount($account)
245
    {
246
        $this->account = $account;
247
        return $this;
248
    }
249
250
    /**
251
     * [getAccount Get the requests account]
252
     */
253
    public function getAccount()
254
    {
255
        if(is_null($this->account)) {
256
            (new exception\errorException)
257
                ->setOptions($this->getOptions())
258
                ->error('UNAUTHORIZED');
259
            return;
260
        }
261
262
        return $this->account;
263
    }
264
265
    /**
266
     * [options Responsible API options]
267
     * @param array $options
268
     */
269
    public function options($options)
270
    {
271
        $this->options = $options;
272
        return $this;
273
    }
274
275
    /**
276
     * [getOptions Get the stored Responsible API options]
277
     * @return array
278
     */
279
    private function getOptions()
280
    {
281
        return $this->options;
282
    }
283
284
    /**
285
     * [hasOptionProperty Check if an option property is set]
286
     * @param  array  $options
287
     * @param  string  $property
288
     * @return boolean
289
     */
290
    private function hasOptionProperty(array $options, $property, $default = false)
291
    {
292
        $val = isset($options[$property]) ? $options[$property] : $default;
293
294
        if ($val && empty($options[$property])) {
295
            $val = $default;
296
        }
297
298
        return $val;
299
    }
300
301
    /**
302
     * [setCapacity Set the buckets capacity]
303
     * @param array $options
304
     */
305
    public function setCapacity($options)
306
    {
307
        $hasCapacityOption = $this->hasOptionProperty($options, 'rateLimit');
308
309
        if ($hasCapacityOption) {
310
            if (!is_integer($hasCapacityOption)||empty($hasCapacityOption)) {
0 ignored issues
show
introduced by
The condition is_integer($hasCapacityOption) is always false.
Loading history...
311
                $hasCapacityOption = false;
312
            }
313
        }
314
315
        $this->capacity = ($hasCapacityOption) ? $hasCapacityOption : $this->capacity;
0 ignored issues
show
introduced by
The condition $hasCapacityOption is always false.
Loading history...
316
    }
317
318
    /**
319
     * [getCapacity Get the buckets capacity]
320
     * @return integer
321
     */
322
    public function getCapacity()
323
    {
324
        return $this->capacity;
325
    }
326
327
    /**
328
     * [setTimeframe Set the window timeframe]
329
     * @param array $options
330
     */
331
    public function setTimeframe($options)
332
    {
333
        $timeframe = $this->hasOptionProperty($options, 'rateWindow');
334
335
        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...
336
            $timeframe = $this->window;
337
        }
338
339
        if (!$timeframe) {
340
            $timeframe = $this->window;
341
        }
342
343
        if (is_numeric($timeframe)) {
344
            self::$timeframe['CUSTOM'] = $timeframe;
345
            $this->window = self::$timeframe['CUSTOM'];
346
            return;
347
        }
348
349
        if (isset(self::$timeframe[$timeframe])) {
350
            $this->window = self::$timeframe[$timeframe];
351
            return;
352
        }
353
354
        $this->window = self::$timeframe['MINUTE'];
355
    }
356
357
    /**
358
     * [getTimeframe Get the timeframe window]
359
     * @return integer|string
360
     */
361
    public function getTimeframe()
362
    {
363
        return $this->window;
364
    }
365
366
    /**
367
     * [setLeakRate Set the buckets leak rate]
368
     * Options: slow, medium, normal, default, fast or custom positive integer
369
     * @param array $options
370
     */
371
    private function setLeakRate($options)
372
    {
373
        if (isset($options['leak']) && !$options['leak']) {
374
            $options['leakRate'] = 'default';
375
        }
376
377
        $leakRate = $this->hasOptionProperty($options, 'leakRate');
378
379
        if (empty($leakRate) || !is_string($leakRate)) {
0 ignored issues
show
introduced by
The condition is_string($leakRate) is always false.
Loading history...
380
            $leakRate = 'default';
381
        }
382
383
        $this->leakRate = $leakRate;
384
    }
385
386
    /**
387
     * [getLeakRate Get the buckets leak rate]
388
     * @return string|integer
389
     */
390
    private function getLeakRate()
391
    {
392
        return $this->leakRate;
393
    }
394
395
    /**
396
     * [setUnlimited Rate limiter bypass]
397
     * @param array $options
398
     */
399
    private function setUnlimited($options)
400
    {
401
        $unlimited = false;
402
403
        if(isset($options['unlimited']) && ($options['unlimited'] == 1 || $options['unlimited'] == true)) {
404
            $unlimited = true;
405
        }
406
407
        if (isset($options['requestType']) && $options['requestType'] === 'debug') {
408
            $unlimited = true;
409
        }
410
411
        $this->unlimited = $unlimited;
412
    }
413
414
    /**
415
     * [isUnlimited Check if the Responsible API is set to unlimited]
416
     * @return boolean
417
     */
418
    private function isUnlimited()
419
    {
420
        return $this->unlimited;
421
    }
422
}
423