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

RateLimiter::setCacheKey()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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