Passed
Push — master ( 3c17ee...f685ec )
by Anatoly
02:54 queued 14s
created

RetryableClient   A

Complexity

Total Complexity 9

Size/Duplication

Total Lines 44
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 9
eloc 13
dl 0
loc 44
rs 10
c 0
b 0
f 0
ccs 16
cts 16
cp 1

4 Methods

Rating   Name   Duplication   Size   Complexity  
A calculateDelay() 0 4 1
A sendRequest() 0 10 4
A __construct() 0 10 3
A applyDelay() 0 3 1
1
<?php
2
3
/**
4
 * It's free open-source software released under the MIT License.
5
 *
6
 * @author Anatoly Nekhay <[email protected]>
7
 * @copyright Copyright (c) 2018, Anatoly Nekhay
8
 * @license https://github.com/sunrise-php/http-client-curl/blob/master/LICENSE
9
 * @link https://github.com/sunrise-php/http-client-curl
10
 */
11
12
declare(strict_types=1);
13
14
namespace Sunrise\Http\Client\Curl\Decorator;
15
16
use InvalidArgumentException;
17
use Psr\Http\Client\ClientInterface;
18
use Psr\Http\Client\NetworkExceptionInterface;
19
use Psr\Http\Message\RequestInterface;
20
use Psr\Http\Message\ResponseInterface;
21
22
use function random_int;
23
use function usleep;
24
25
/**
26
 * @since 2.1.0
27
 */
28
final class RetryableClient implements ClientInterface
29
{
30 8
    public function __construct(
31
        private readonly ClientInterface $baseClient,
32
        private readonly int $maxAttempts,
33
        private readonly int $baseDelay,
34
    ) {
35 8
        if ($maxAttempts < 1) {
36 1
            throw new InvalidArgumentException('maxAttempts must be >= 1');
37
        }
38 7
        if ($baseDelay < 0) {
39 1
            throw new InvalidArgumentException('baseDelay must be >= 0');
40
        }
41
    }
42
43
    /**
44
     * @inheritDoc
45
     */
46 6
    public function sendRequest(RequestInterface $request): ResponseInterface
47
    {
48 6
        $attempt = 0;
49 6
        while (true) {
50 6
            $attempt++;
51
52
            try {
53 6
                return $this->baseClient->sendRequest($request);
54 5
            } catch (NetworkExceptionInterface $e) {
55 5
                $attempt < $this->maxAttempts ? $this->applyDelay($attempt) : throw $e;
56
            }
57
        }
0 ignored issues
show
Bug Best Practice introduced by
In this branch, the function will implicitly return null which is incompatible with the type-hinted return Psr\Http\Message\ResponseInterface. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
58
    }
59
60 4
    private function applyDelay(int $attempt): void
61
    {
62 4
        usleep($this->calculateDelay($attempt));
63
    }
64
65
    /**
66
     * @link https://aws.amazon.com/ru/blogs/architecture/exponential-backoff-and-jitter/
67
     */
68 4
    private function calculateDelay(int $attempt): int
69
    {
70
        // full jitter
71 4
        return random_int(0, $this->baseDelay * (2 ** ($attempt - 1)));
72
    }
73
}
74