Passed
Push — master ( e7e63f...e27985 )
by Vince
01:43 queued 14s
created

limiter::save()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 17
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 1
eloc 6
c 2
b 0
f 0
nc 1
nop 0
dl 0
loc 17
rs 10
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 integer
32
     */
33
    private $leakRate = 1;
34
35
    /**
36
     * [$unpacked]
37
     */
38
    private $unpacked;
39
40
    /**
41
     * [$bucket]
42
     */
43
    private $bucket;
44
45
    /**
46
     * [$timeframe Durations are in seconds]
47
     * @var array
48
     */
49
    private static $timeframe = [
50
        'SECOND' => 1,
51
        'MINUTE' => 60,
52
        'HOUR' => 3600,
53
        'DAY' => 86400,
54
        'CUSTOM' => 0,
55
    ];
56
57
    /**
58
     * [$window Timeframe window]
59
     * @var integer
60
     */
61
    private $window;
62
63
    /**
64
     * [$unlimited Rate limiter bypass if true]
65
     * @var boolean
66
     */
67
    private $unlimited = false;
68
69
    /**
70
     * [$scope Set the default scope]
71
     * @var string
72
     */
73
    private $scope = 'private';
74
75
    /**
76
     * [setupOptions Set any Responsible API options]
77
     * @return self
78
     */
79
    public function setupOptions()
80
    {
81
        $options = $this->getOptions();
82
83
        if (isset($options['rateLimit'])) {
84
            $this->setCapacity($options['rateLimit']);
85
        }
86
87
        if (isset($options['rateWindow'])) {
88
            $this->setTimeframe($options['rateWindow']);
89
        }
90
91
        if (isset($options['leak']) && !$options['leak']) {
92
            $options['leakRate'] = 0;
93
        }
94
95
        if (isset($options['leakRate'])) {
96
            $this->setLeakRate($options['leakRate']);
97
        }
98
99
        if (isset($options['unlimited']) && ($options['unlimited'] == 1 || $options['unlimited'] == true)) {
100
            $this->setUnlimited();
101
        }
102
103
        if (isset($options['requestType']) && $options['requestType'] == 'debug') {
104
            $this->setUnlimited();
105
        }
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 void
119
     */
120
    public function throttleRequest()
121
    {
122
        if ($this->isUnlimited() || $this->scope !== 'private') {
123
            return true;
0 ignored issues
show
Bug Best Practice introduced by
The expression return true returns the type true which is incompatible with the documented return type void.
Loading history...
124
        }
125
126
        /**
127
         * [$unpack Unpack the account bucket data]
128
         */
129
        $this->unpacked = (new throttle\tokenPack)->unpack(
130
            $this->getAccount()->bucket
131
        );
132
        if (empty($this->unpacked)) {
133
            $this->unpacked = array(
134
                'drops' => 1,
135
                'time' => $this->getAccount()->access,
136
            );
137
        }
138
139
        $this->bucket = (new throttle\tokenBucket())
140
            ->setTimeframe($this->getTimeframe())
141
            ->setCapacity($this->getCapacity())
142
            ->setLeakRate($this->getLeakRate())
143
            ->pour($this->unpacked['drops'], $this->unpacked['time'])
144
        ;
145
146
        /**
147
         * Check if the bucket still has capacity to fill
148
         */
149
        if ($this->bucket->capacity()) {
150
            $this->bucket->pause(false);
151
            $this->bucket->fill();
152
        } else {
153
            if ($this->getLeakRate() <= 0) {
154
                if ($this->unpacked['pauseAccess'] == false) {
155
                    $this->bucket->pause(true);
156
                    $this->save();
157
                }
158
159
                if ($this->bucket->refill($this->getAccount()->access)) {
160
                    $this->save();
161
                }
162
            }
163
164
            (new exception\errorException)->error('TOO_MANY_REQUESTS');
165
        }
166
167
        $this->save();
168
    }
169
170
    /**
171
     * [updateBucket Store the buckets token data and user access time]
172
     * @return void
173
     */
174
    private function save()
175
    {
176
        $this->packed = (new throttle\tokenPack)->pack(
0 ignored issues
show
Bug Best Practice introduced by
The property packed does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
177
            $this->bucket->getTokenData()
178
        );
179
180
        /**
181
         * [Update account access]
182
         */
183
        /*$user = (new user\account($this->getAccount()->account_id))
184
            ->setBucketToken($this->packed)
185
            ->updateAccountAccess();*/
186
187
        $user = (new user\user)
0 ignored issues
show
Unused Code introduced by
The assignment to $user is dead and can be removed.
Loading history...
Bug introduced by
Are you sure the assignment to $user is correct as new responsible\core\use...->updateAccountAccess() targeting responsible\core\user\user::updateAccountAccess() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
188
            ->setAccountID($this->getAccount()->account_id)
189
            ->setBucketToken($this->packed)
190
            ->updateAccountAccess()
191
        ;
192
    }
193
194
    /**
195
     * [getThrottle Return a list of the throttled results]
196
     * @return array
197
     */
198
    public function getThrottle()
199
    {
200
        if ($this->isUnlimited() || $this->scope !== 'private') {
201
            return array(
202
                'unlimited' => true,
203
            );
204
        }
205
206
        $windowFrame = (is_string($this->getTimeframe()))
0 ignored issues
show
introduced by
The condition is_string($this->getTimeframe()) is always false.
Loading history...
207
        ? $this->getTimeframe()
208
        : $this->getTimeframe() . 'secs'
209
        ;
210
211
        return array(
212
            'limit' => $this->getCapacity(),
213
            'leakRate' => $this->getLeakRate(),
214
            'leak' => $this->bucket->getLeakage(),
215
            'lastAccess' => $this->getLastAccessDate(),
216
            'description' => $this->getCapacity() . ' requests per ' . $windowFrame,
217
            'bucket' => $this->bucket->getTokenData(),
218
        );
219
    }
220
221
    /**
222
     * [getLastAccessDate Get the last recorded access in date format]
223
     * @return string
224
     */
225
    private function getLastAccessDate()
226
    {
227
        if (isset($this->bucket->getTokenData()['time'])) {
228
            return date('m/d/y h:i:sa', $this->bucket->getTokenData()['time']);
229
        }
230
231
        return 'Can\'t be converted';
232
    }
233
234
    /**
235
     * [setAccount Set the requests account]
236
     */
237
    public function setAccount($account)
238
    {
239
        $this->account = $account;
0 ignored issues
show
Bug Best Practice introduced by
The property account does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
240
        return $this;
241
    }
242
243
    /**
244
     * [getAccount Get the requests account]
245
     */
246
    public function getAccount()
247
    {
248
        return $this->account;
249
    }
250
251
    /**
252
     * [options Responsible API options]
253
     * @param array $options
254
     */
255
    public function options($options)
256
    {
257
        $this->options = $options;
0 ignored issues
show
Bug Best Practice introduced by
The property options does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
258
        return $this;
259
    }
260
261
    /**
262
     * [getOptions Get the stored Responsible API options]
263
     * @return array
264
     */
265
    private function getOptions()
266
    {
267
        return $this->options;
268
    }
269
270
    /**
271
     * [setCapacity Set the buckets capacity]
272
     * @param integer $capacity
273
     */
274
    public function setCapacity($capacity)
275
    {
276
        $this->capacity = $capacity;
277
    }
278
279
    /**
280
     * [getCapacity Get the buckets capacity]
281
     * @return integer
282
     */
283
    public function getCapacity()
284
    {
285
        return $this->capacity;
286
    }
287
288
    /**
289
     * [setTimeframe Set the window timeframe]
290
     * @param string|integer $timeframe
291
     */
292
    public function setTimeframe($timeframe)
293
    {
294
        if (is_numeric($timeframe)) {
295
            self::$timeframe['CUSTOM'] = $timeframe;
296
            $this->window = self::$timeframe['CUSTOM'];
0 ignored issues
show
Documentation Bug introduced by
It seems like self::timeframe['CUSTOM'] can also be of type string. However, the property $window is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
297
            return;
298
        }
299
300
        if (isset(self::$timeframe[$timeframe])) {
301
            $this->window = self::$timeframe[$timeframe];
302
            return;
303
        }
304
305
        $this->window = self::$timeframe['MINUTE'];
306
    }
307
308
    /**
309
     * [getTimeframe Get the timeframe window]
310
     * @return integer
311
     */
312
    public function getTimeframe()
313
    {
314
        return $this->window;
315
    }
316
317
    /**
318
     * [setLeakRate Set the buckets leak rate]
319
     * Options: slow, medium, normal, default, fast or custom positive integer
320
     * @param string|integer $leakRate
321
     */
322
    private function setLeakRate($leakRate)
323
    {
324
        $this->leakRate = $leakRate;
0 ignored issues
show
Documentation Bug introduced by
It seems like $leakRate can also be of type string. However, the property $leakRate is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
325
    }
326
327
    /**
328
     * [getLeakRate Get the buckets leak rate]
329
     * @return string|integer
330
     */
331
    private function getLeakRate()
332
    {
333
        return $this->leakRate;
334
    }
335
336
    /**
337
     * [setUnlimited Rate limiter bypass]
338
     */
339
    private function setUnlimited()
340
    {
341
        $this->unlimited = true;
342
    }
343
344
    /**
345
     * [isUnlimited Check if the Responsible API is set to unlimited]
346
     * @return boolean
347
     */
348
    private function isUnlimited()
349
    {
350
        return $this->unlimited;
351
    }
352
}
353