Passed
Push — master ( 734e09...9157ea )
by Timo
02:34
created

RequestLimiter::matches()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace hamburgscleanest\GuzzleAdvancedThrottle;
4
5
use GuzzleHttp\Psr7\Uri;
6
use hamburgscleanest\GuzzleAdvancedThrottle\Cache\Adapters\ArrayAdapter;
7
use hamburgscleanest\GuzzleAdvancedThrottle\Cache\Interfaces\StorageInterface;
8
use hamburgscleanest\GuzzleAdvancedThrottle\Exceptions\HostNotDefinedException;
9
use Psr\Http\Message\RequestInterface;
10
11
/**
12
 * Class RequestLimiter
13
 * @package hamburgscleanest\GuzzleAdvancedThrottle
14
 */
15
class RequestLimiter
16
{
17
18
    /** @var int */
19
    private const DEFAULT_MAX_REQUESTS = 120;
20
    /** @var int */
21
    private const DEFAULT_REQUEST_INTERVAL = 60;
22
    /** @var string */
23
    private $_host;
24
    /** @var TimeKeeper */
25
    private $_timekeeper;
26
    /** @var int */
27
    private $_requestCount = 0;
28
    /** @var int */
29
    private $_maxRequestCount;
30
    /** @var StorageInterface */
31
    private $_storage;
32
    /** @var string */
33
    private $_storageKey;
34
35
    /**
36
     * RequestLimiter constructor.
37
     * @param string $host
38
     * @param int $maxRequests
39
     * @param int $requestIntervalSeconds
40
     * @param StorageInterface|null $storage
41
     * @throws \Exception
42
     */
43 19
    public function __construct(string $host, ?int $maxRequests = self::DEFAULT_MAX_REQUESTS, ?int $requestIntervalSeconds = self::DEFAULT_REQUEST_INTERVAL, StorageInterface $storage = null)
44
    {
45 19
        $this->_storage = $storage ?? new ArrayAdapter();
46 19
        $this->_host = $host;
47 19
        $this->_maxRequestCount = $maxRequests ?? self::DEFAULT_MAX_REQUESTS;
48 19
        $requestInterval = $requestIntervalSeconds ?? self::DEFAULT_REQUEST_INTERVAL;
49
50 19
        $this->_storageKey = $maxRequests . '_' . $requestInterval;
51 19
        $this->_restoreState($requestInterval);
52 19
    }
53
54
    /**
55
     * @param int $requestIntervalSeconds
56
     * @throws \Exception
57
     */
58 19
    private function _restoreState(int $requestIntervalSeconds) : void
59
    {
60 19
        $this->_timekeeper = new TimeKeeper($requestIntervalSeconds);
61
62 19
        $requestInfo = $this->_storage->get($this->_host, $this->_storageKey);
63 19
        if ($requestInfo === null)
64
        {
65 19
            return;
66
        }
67
68 5
        $this->_requestCount = $requestInfo->requestCount;
69 5
        $this->_timekeeper->setExpiration($requestInfo->expiresAt);
70 5
    }
71
72
    /**
73
     * @param array $rule
74
     * @param StorageInterface|null $storage
75
     * @return RequestLimiter
76
     * @throws \hamburgscleanest\GuzzleAdvancedThrottle\Exceptions\HostNotDefinedException
77
     * @throws \Exception
78
     */
79 9
    public static function createFromRule(array $rule, StorageInterface $storage = null) : self
80
    {
81 9
        if (!isset($rule['host']))
82
        {
83 1
            throw new HostNotDefinedException();
84
        }
85
86 8
        return new static($rule['host'], $rule['max_requests'] ?? null, $rule['request_interval'] ?? null, $storage);
87
    }
88
89
    /**
90
     * @param string $host
91
     * @param int $maxRequests
92
     * @param int $requestIntervalSeconds
93
     * @param StorageInterface|null $storage
94
     * @return RequestLimiter
95
     * @throws \Exception
96
     */
97 7
    public static function create(string $host, ?int $maxRequests = self::DEFAULT_MAX_REQUESTS, ?int $requestIntervalSeconds = self::DEFAULT_REQUEST_INTERVAL, StorageInterface $storage = null) : self
98
    {
99 7
        return new static($host, $maxRequests, $requestIntervalSeconds, $storage);
100
    }
101
102
    /**
103
     * @param RequestInterface $request
104
     * @param array $options
105
     * @return bool
106
     * @throws \Exception
107
     */
108 14
    public function canRequest(RequestInterface $request, array $options = []) : bool
109
    {
110 14
        if (!$this->matches($this->_getHostFromRequestAndOptions($request, $options)))
111
        {
112
            return true;
113
        }
114
115 14
        if ($this->_requestCount >= $this->_maxRequestCount)
116
        {
117 10
            return false;
118
        }
119
120 11
        $this->_increment();
121 11
        $this->_save();
122
123 11
        return true;
124
    }
125
126
    /**
127
     * @param string $host
128
     * @return bool
129
     */
130 15
    public function matches(string $host) : bool
131
    {
132 15
        return $this->_host === $host;
133
    }
134
135
    /**
136
     * @param RequestInterface $request
137
     * @param array $options
138
     * @return string
139
     */
140 14
    private function _getHostFromRequestAndOptions(RequestInterface $request, array $options = []) : string
141
    {
142 14
        $uri = $options['base_uri'] ?? $request->getUri();
143
144 14
        return $this->_buildHostUrl($uri);
145
    }
146
147
    /**
148
     * @param Uri $uri
149
     * @return string
150
     */
151 14
    private function _buildHostUrl(Uri $uri) : string
152
    {
153 14
        $host = $uri->getHost();
154 14
        $scheme = $uri->getScheme();
155 14
        if (!empty($host) && !empty($scheme))
156
        {
157 8
            $host = $scheme . '://' . $host;
158
        } else
159
        {
160 6
            $host = $uri->getPath();
161
        }
162
163 14
        return $host;
164
    }
165
166
    /**
167
     * Increment the request counter.
168
     * @throws \Exception
169
     */
170 11
    private function _increment() : void
171
    {
172 11
        $this->_requestCount ++;
173 11
        if ($this->_requestCount === 1)
174
        {
175 11
            $this->_timekeeper->start();
176
        }
177 11
    }
178
179
    /**
180
     * @throws \hamburgscleanest\GuzzleAdvancedThrottle\Exceptions\TimerNotStartedException
181
     */
182 11
    private function _save() : void
183
    {
184 11
        $this->_storage->save(
185 11
            $this->_host,
186 11
            $this->_storageKey,
187 11
            $this->_requestCount,
188 11
            $this->_timekeeper->getExpiration(),
189 11
            $this->getRemainingSeconds()
190
        );
191 11
    }
192
193
    /**
194
     * @return int
195
     * @throws \hamburgscleanest\GuzzleAdvancedThrottle\Exceptions\TimerNotStartedException
196
     */
197 14
    public function getRemainingSeconds() : int
198
    {
199 14
        return $this->_timekeeper->getRemainingSeconds();
200
    }
201
202
    /**
203
     * @return int
204
     * @throws \hamburgscleanest\GuzzleAdvancedThrottle\Exceptions\TimerNotStartedException
205
     */
206 3
    public function getCurrentRequestCount() : int
207
    {
208 3
        if ($this->_timekeeper->isExpired())
209
        {
210 1
            $this->_timekeeper->reset();
211 1
            $this->_requestCount = 0;
212
        }
213
214 3
        return $this->_requestCount;
215
    }
216
}