Completed
Push — master ( 553806...856339 )
by Krishnaprasad
02:34
created

MovingWindowThrottler   A

Complexity

Total Complexity 11

Size/Duplication

Total Lines 86
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Test Coverage

Coverage 94.29%

Importance

Changes 0
Metric Value
wmc 11
lcom 1
cbo 3
dl 0
loc 86
ccs 33
cts 35
cp 0.9429
rs 10
c 0
b 0
f 0

5 Methods

Rating   Name   Duplication   Size   Complexity  
A hit() 0 15 2
A count() 0 6 1
A getRetryTimeout() 0 16 4
A clear() 0 5 1
A updateHitCount() 0 18 3
1
<?php
2
/**
3
 * The MIT License (MIT)
4
 *
5
 * Copyright (c) 2015 Krishnaprasad MG <[email protected]>
6
 *
7
 * Permission is hereby granted, free of charge, to any person obtaining a copy
8
 * of this software and associated documentation files (the "Software"), to deal
9
 * in the Software without restriction, including without limitation the rights
10
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
 * copies of the Software, and to permit persons to whom the Software is
12
 * furnished to do so, subject to the following conditions:
13
 *
14
 * The above copyright notice and this permission notice shall be included in all
15
 * copies or substantial portions of the Software.
16
 *
17
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
 * SOFTWARE.
24
 */
25
26
namespace Sunspikes\Ratelimit\Throttle\Throttler;
27
28
use Sunspikes\Ratelimit\Cache\Exception\ItemNotFoundException;
29
30
final class MovingWindowThrottler extends AbstractWindowThrottler implements RetriableThrottlerInterface
31
{
32
    /**
33
     * [Timestamp => recorded hits]
34
     *
35
     * @var array
36
     */
37
    private $hitCountMapping = [];
38
39
    /**
40
     * @inheritdoc
41
     */
42 7
    public function hit()
43
    {
44 7
        $timestamp = (int) ceil($this->timeProvider->now());
45 7
        $this->updateHitCount();
46
47 7
        if (!isset($this->hitCountMapping[$timestamp])) {
48 7
            $this->hitCountMapping[$timestamp] = 0;
49 7
        }
50
51
        //Adds 1 recorded hit to the mapping entry for the current timestamp
52 7
        $this->hitCountMapping[$timestamp]++;
53 7
        $this->cache->set($this->key, serialize($this->hitCountMapping), $this->cacheTtl);
54
55 7
        return $this;
56
    }
57
58
    /**
59
     * @inheritdoc
60
     */
61 13
    public function count()
62
    {
63 13
        $this->updateHitCount();
64
65 13
        return (int) array_sum($this->hitCountMapping);
66
    }
67
68
    /**
69
     * @inheritdoc
70
     */
71 2
    public function getRetryTimeout()
72
    {
73 2
        if ($this->hitLimit > $totalHitCount = $this->count()) {
74 1
            return 0;
75
        }
76
77
        // Check at which 'oldest' possible timestamp enough hits have expired
78
        // Then return the time remaining for that timestamp to expire
79 1
        foreach ($this->hitCountMapping as $timestamp => $hitCount) {
80 1
            if ($this->hitLimit > $totalHitCount -= $hitCount) {
81 1
                return 1e3 * max(0, $this->timeLimit - ((int) ceil($this->timeProvider->now()) - $timestamp));
82
            }
83
        }
84
85
        return 1e3 * $this->timeLimit;
86
    }
87
88
    /**
89
     * @inheritdoc
90
     */
91 2
    public function clear()
92
    {
93 2
        $this->hitCountMapping = [];
94 2
        $this->cache->set($this->key, serialize([]), $this->cacheTtl);
95 2
    }
96
97 13
    private function updateHitCount()
98
    {
99
        try {
100
            // Get a stored mapping from cache
101 13
            if (0 === count($this->hitCountMapping)) {
102 13
                $this->hitCountMapping = (array) unserialize($this->cache->get($this->key));
103 7
            }
104 13
        } catch (ItemNotFoundException $exception) {}
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
105
106 13
        $startTime = (int) ceil($this->timeProvider->now()) - $this->timeLimit;
107
108
        // Clear all entries older than the window front-edge
109 13
        $relevantTimestamps = array_filter(array_keys($this->hitCountMapping), function ($key) use ($startTime) {
110 9
            return $startTime <= $key;
111 13
        });
112
113 13
        $this->hitCountMapping = array_intersect_key($this->hitCountMapping, array_flip($relevantTimestamps));
114 13
    }
115
}
116