Passed
Push — master ( 05cdaf...2d675e )
by BENOIT
13:52 queued 10:50
created

FunnelHttpClient   A

Complexity

Total Complexity 10

Size/Duplication

Total Lines 77
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 10
eloc 22
c 1
b 0
f 0
dl 0
loc 77
rs 10

6 Methods

Rating   Name   Duplication   Size   Complexity  
A withOptions() 0 11 2
A __construct() 0 8 1
A stream() 0 3 1
A waitUntilReady() 0 9 2
A throttle() 0 3 1
A request() 0 13 3
1
<?php
2
3
namespace BenTools\FunnelHttpClient;
4
5
use BenTools\FunnelHttpClient\Storage\ArrayStorage;
6
use BenTools\FunnelHttpClient\Storage\ThrottleStorageInterface;
7
use BenTools\FunnelHttpClient\Strategy\AlwaysThrottleStrategy;
8
use BenTools\FunnelHttpClient\Strategy\ThrottleStrategyInterface;
9
use LogicException;
10
use Psr\Log\LoggerInterface;
11
use Psr\Log\NullLogger;
12
use Symfony\Contracts\HttpClient\HttpClientInterface;
13
use Symfony\Contracts\HttpClient\ResponseInterface;
14
use Symfony\Contracts\HttpClient\ResponseStreamInterface;
15
16
use function method_exists;
17
18
final class FunnelHttpClient implements HttpClientInterface
19
{
20
    public function __construct(
21
        private HttpClientInterface $decorated,
22
        private ThrottleStorageInterface $throttleStorage,
23
        private ?ThrottleStrategyInterface $throttleStrategy = null,
24
        private ?LoggerInterface $logger = null
25
    ) {
26
        $this->throttleStrategy = $throttleStrategy ?? new AlwaysThrottleStrategy();
27
        $this->logger = $logger ?? new NullLogger();
28
    }
29
30
    public function withOptions(array $options): static
31
    {
32
        // SF 5+ compatibility
33
        if (!method_exists($this->decorated, 'withOptions')) {
34
            throw new LogicException('%s doesn\'t implement `withOptions`.');
35
        }
36
37
        $clone = clone $this;
38
        $clone->decorated = $clone->decorated->withOptions($options);
39
40
        return $clone;
41
    }
42
43
    /**
44
     * @inheritDoc
45
     */
46
    public function request(string $method, string $url, array $options = []): ResponseInterface
47
    {
48
        if (!$this->throttleStrategy->shouldThrottle($method, $url, $options)) {
0 ignored issues
show
Bug introduced by
The method shouldThrottle() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

48
        if (!$this->throttleStrategy->/** @scrutinizer ignore-call */ shouldThrottle($method, $url, $options)) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
49
            return $this->decorated->request($method, $url, $options);
50
        }
51
52
        if (0 === $this->throttleStorage->getRemainingCalls()) {
53
            $this->waitUntilReady($method, $url);
54
        }
55
56
        $response = $this->decorated->request($method, $url, $options);
57
        $this->throttleStorage->increment();
58
        return $response;
59
    }
60
61
    /**
62
     * @inheritDoc
63
     */
64
    public function stream($responses, float $timeout = null): ResponseStreamInterface
65
    {
66
        return $this->decorated->stream($responses, $timeout);
67
    }
68
69
    /**
70
     * @param string $method
71
     * @param string $url
72
     */
73
    private function waitUntilReady(string $method, string $url): void
74
    {
75
        $remainingSeconds = $this->throttleStorage->getRemainingTime();
76
        $this->logger->info(\sprintf('Max requests / window reached. Waiting %s seconds...', $remainingSeconds), ['method' => $method, 'url' => $url]);
0 ignored issues
show
Bug introduced by
The method info() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

76
        $this->logger->/** @scrutinizer ignore-call */ 
77
                       info(\sprintf('Max requests / window reached. Waiting %s seconds...', $remainingSeconds), ['method' => $method, 'url' => $url]);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
77
78
        if (0 === ($remainingSeconds <=> (int) $remainingSeconds)) {
79
            \sleep((int) $remainingSeconds);
80
        } else {
81
            \usleep((int) \round($remainingSeconds * 1000000));
82
        }
83
    }
84
85
    /**
86
     * @param HttpClientInterface  $client
87
     * @param int                  $maxRequests
88
     * @param float                $timeWindow
89
     * @param LoggerInterface|null $logger
90
     * @return FunnelHttpClient
91
     */
92
    public static function throttle(HttpClientInterface $client, int $maxRequests, float $timeWindow, ?LoggerInterface $logger = null): self
93
    {
94
        return new self($client, new ArrayStorage($maxRequests, $timeWindow), null, $logger);
95
    }
96
}
97