Issues (2)

src/FunnelHttpClient.php (2 issues)

Labels
Severity
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\Exception\TransportExceptionInterface;
13
use Symfony\Contracts\HttpClient\HttpClientInterface;
14
use Symfony\Contracts\HttpClient\ResponseInterface;
15
use Symfony\Contracts\HttpClient\ResponseStreamInterface;
16
17
use function method_exists;
18
19
final class FunnelHttpClient implements HttpClientInterface
20
{
21
    public function __construct(
22
        private HttpClientInterface $decorated,
23
        private ThrottleStorageInterface $throttleStorage,
24
        private ?ThrottleStrategyInterface $throttleStrategy = null,
25
        private ?LoggerInterface $logger = null
26
    ) {
27
        $this->throttleStrategy = $throttleStrategy ?? new AlwaysThrottleStrategy();
28
        $this->logger = $logger ?? new NullLogger();
29
    }
30
31
    public function withOptions(array $options): static
32
    {
33
        // SF 5+ compatibility
34
        if (!method_exists($this->decorated, 'withOptions')) {
35
            throw new LogicException('%s doesn\'t implement `withOptions`.');
36
        }
37
38
        $clone = clone $this;
39
        $clone->decorated = $clone->decorated->withOptions($options);
40
41
        return $clone;
42
    }
43
44
    /**
45
     * @inheritDoc
46
     */
47
    public function request(string $method, string $url, array $options = []): ResponseInterface
48
    {
49
        if (!$this->throttleStrategy->shouldThrottle($method, $url, $options)) {
0 ignored issues
show
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

49
        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...
50
            return $this->decorated->request($method, $url, $options);
51
        }
52
53
        if (0 === $this->throttleStorage->getRemainingCalls()) {
54
            $this->waitUntilReady($method, $url);
55
        }
56
57
        $this->throttleStorage->increment();
58
        try {
59
            $response = $this->decorated->request($method, $url, $options);
60
        } catch (TransportExceptionInterface $e) {
61
            $this->throttleStorage->decrement();
62
            throw $e;
63
        }
64
65
        return $response;
66
    }
67
68
    /**
69
     * @inheritDoc
70
     */
71
    public function stream($responses, float $timeout = null): ResponseStreamInterface
72
    {
73
        return $this->decorated->stream($responses, $timeout);
74
    }
75
76
    /**
77
     * @param string $method
78
     * @param string $url
79
     */
80
    private function waitUntilReady(string $method, string $url): void
81
    {
82
        $remainingSeconds = $this->throttleStorage->getRemainingTime();
83
        $this->logger->info(\sprintf('Max requests / window reached. Waiting %s seconds...', $remainingSeconds), ['method' => $method, 'url' => $url]);
0 ignored issues
show
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

83
        $this->logger->/** @scrutinizer ignore-call */ 
84
                       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...
84
85
        if (0 === ($remainingSeconds <=> (int) $remainingSeconds)) {
86
            \sleep((int) $remainingSeconds);
87
        } else {
88
            \usleep((int) \round($remainingSeconds * 1000000));
89
        }
90
    }
91
92
    /**
93
     * @param HttpClientInterface  $client
94
     * @param int                  $maxRequests
95
     * @param float                $timeWindow
96
     * @param LoggerInterface|null $logger
97
     * @return FunnelHttpClient
98
     */
99
    public static function throttle(HttpClientInterface $client, int $maxRequests, float $timeWindow, ?LoggerInterface $logger = null): self
100
    {
101
        return new self($client, new ArrayStorage($maxRequests, $timeWindow), null, $logger);
102
    }
103
}
104