Passed
Pull Request — master (#203)
by Alexander
02:02
created

RateLimiter::setupCacheKey()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 2
eloc 3
c 1
b 0
f 1
nc 2
nop 1
dl 0
loc 7
rs 10
1
<?php
2
declare(strict_types=1);
3
4
namespace Yiisoft\Yii\Web\Middleware;
5
6
use Psr\Http\Message\ResponseFactoryInterface;
7
use Psr\Http\Message\ResponseInterface;
8
use Psr\Http\Message\ServerRequestInterface;
9
use Psr\Http\Server\MiddlewareInterface;
10
use Psr\Http\Server\RequestHandlerInterface;
11
use Psr\SimpleCache\CacheInterface;
12
13
/**
14
 * Rate limiter limits the number of requests that could be made within a certain period of time
15
 */
16
final class RateLimiter implements MiddlewareInterface
17
{
18
    private int $limit = 1000;
19
20
    private ?string $cacheKey = null;
21
22
    /**
23
     * @var callable
24
     */
25
    private $cacheKeyCallback;
26
27
    private int $cacheTtl = 360;
28
29
    private CacheInterface $cache;
30
31
    private ResponseFactoryInterface $responseFactory;
32
33
    private bool $autoincrement = true;
34
35
    public function __construct(CacheInterface $cache, ResponseFactoryInterface $responseFactory)
36
    {
37
        $this->cache = $cache;
38
        $this->responseFactory = $responseFactory;
39
    }
40
41
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
42
    {
43
        $this->setupCacheParams($request);
44
45
        if (!$this->isAllowed()) {
46
            return $this->createErrorResponse();
47
        }
48
49
        if ($this->autoincrement) {
50
            $this->increment();
51
        }
52
53
        return $handler->handle($request);
54
    }
55
56
    public function setLimit(int $limit): self
57
    {
58
        $this->limit = $limit;
59
60
        return $this;
61
    }
62
63
    public function setCacheKey(string $key): self
64
    {
65
        $this->cacheKey = $key;
66
67
        return $this;
68
    }
69
70
    public function setCacheKeyByCallback(callable $callback): self
71
    {
72
        $this->cacheKeyCallback = $callback;
73
74
        return $this;
75
    }
76
77
    public function setCacheTtl(int $ttl): self
78
    {
79
        $this->cacheTtl = $ttl;
80
81
        return $this;
82
    }
83
84
    public function setAutoIncrement(bool $increment): self
85
    {
86
        $this->autoincrement = $increment;
87
88
        return $this;
89
    }
90
91
    private function createErrorResponse(): ResponseInterface
92
    {
93
        $response = $this->responseFactory->createResponse(429);
94
        $response->getBody()->write('Too Many Requests');
95
96
        return $response;
97
    }
98
99
    private function isAllowed(): bool
100
    {
101
        return $this->getCounterValue() < $this->limit;
102
    }
103
104
    private function increment(): void
105
    {
106
        $value = $this->getCounterValue();
107
        $value++;
108
109
        $this->setCounterValue($value);
110
    }
111
112
    private function setupCacheParams(ServerRequestInterface $request): void
113
    {
114
        $this->cacheKey = $this->setupCacheKey($request);
115
116
        if (!$this->hasCounterValue()) {
117
            $this->setCounterValue(0);
118
        }
119
    }
120
121
    private function setupCacheKey(ServerRequestInterface $request): string
122
    {
123
        if ($this->cacheKeyCallback !== null) {
124
            return \call_user_func($this->cacheKeyCallback, $request);
125
        }
126
127
        return $this->cacheKey ?? $this->generateCacheKey($request);
128
    }
129
130
    private function generateCacheKey(ServerRequestInterface $request): string
131
    {
132
        return strtolower('rate-limiter-' . $request->getMethod() . '-' . $request->getUri()->getPath());
133
    }
134
135
    private function getCounterValue(): int
136
    {
137
        return $this->cache->get($this->cacheKey, 0);
138
    }
139
140
    private function setCounterValue(int $value): void
141
    {
142
        $this->cache->set($this->cacheKey, $value, $this->cacheTtl);
143
    }
144
145
    private function hasCounterValue(): bool
146
    {
147
        return $this->cache->has($this->cacheKey);
148
    }
149
}
150