ClientFactory::fake()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 21
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 10
nc 1
nop 2
dl 0
loc 21
ccs 11
cts 11
cp 1
crap 1
rs 9.9332
c 1
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Cerbero\LazyJsonPages\Services;
6
7
use Cerbero\LazyJsonPages\Middleware\Tap;
8
use Closure;
9
use GuzzleHttp\Client;
10
use GuzzleHttp\Exception\RequestException;
11
use GuzzleHttp\Handler\MockHandler;
12
use GuzzleHttp\HandlerStack;
13
use GuzzleHttp\Middleware;
14
use GuzzleHttp\RequestOptions;
15
use Psr\Http\Message\ResponseInterface;
16
17
/**
18
 * The HTTP client factory.
19
 */
20
final class ClientFactory
21
{
22
    /**
23
     * The default client configuration.
24
     *
25
     * @var array<string, mixed>
26
     */
27
    private static array $defaultConfig = [
28
        RequestOptions::CONNECT_TIMEOUT => 5,
29
        RequestOptions::READ_TIMEOUT => 5,
30
        RequestOptions::TIMEOUT => 5,
31
        RequestOptions::STREAM => true,
32
        RequestOptions::HEADERS => [
33
            'Accept' => 'application/json',
34
            'Content-Type' => 'application/json',
35
        ],
36
    ];
37
38
    /**
39
     * The global middleware.
40
     *
41
     * @var array<string, callable>
42
     */
43
    private static array $globalMiddleware = [];
44
45
    /**
46
     * Whether HTTP requests are faked.
47
     */
48
    private static bool $isFake = false;
49
50
    /**
51
     * The faked rate limits timestamps.
52
     *
53
     * @var float[]
54
     */
55
    public static array $fakedRateLimits = [];
56
57
    /**
58
     * The tap middleware callbacks.
59
     */
60
    private readonly TapCallbacks $tapCallbacks;
61
62
    /**
63
     * The rate limits.
64
     */
65
    public readonly RateLimits $rateLimits;
66
67
    /**
68
     * The custom client configuration.
69
     *
70
     * @var array<string, mixed>
71
     */
72
    private array $config = [];
73
74
    /**
75
     * The local middleware.
76
     *
77
     * @var array<string, callable>
78
     */
79
    private array $middleware = [];
80
81
    /**
82
     * Add a global middleware.
83
     */
84 2
    public static function globalMiddleware(string $name, callable $middleware): void
85
    {
86 2
        self::$globalMiddleware[$name] = $middleware;
87
    }
88
89
    /**
90
     * Fake HTTP requests for testing purposes.
91
     *
92
     * @param ResponseInterface[]|RequestException[] $responses
93
     * @return array<int, array<string, mixed>>
94
     */
95 46
    public static function fake(array $responses, Closure $callback): array
96
    {
97 46
        $transactions = [];
98
99 46
        self::$isFake = true;
100
101 46
        $handler = HandlerStack::create(new MockHandler($responses));
102
103 46
        $handler->push(Middleware::history($transactions));
104
105 46
        self::$defaultConfig['handler'] = $handler;
106
107 46
        $callback();
108
109 43
        unset(self::$defaultConfig['handler']);
110
111 43
        self::$fakedRateLimits = [];
112
113 43
        self::$isFake = false;
114
115 43
        return $transactions;
116
    }
117
118
    /**
119
     * Determine whether the HTTP requests are faked.
120
     */
121 1
    public static function isFake(): bool
122
    {
123 1
        return self::$isFake;
124
    }
125
126
    /**
127
     * Instantiate the class.
128
     */
129 50
    public function __construct()
130
    {
131 50
        $this->tapCallbacks = new TapCallbacks();
0 ignored issues
show
Bug introduced by
The property tapCallbacks is declared read-only in Cerbero\LazyJsonPages\Services\ClientFactory.
Loading history...
132 50
        $this->rateLimits = new RateLimits();
0 ignored issues
show
Bug introduced by
The property rateLimits is declared read-only in Cerbero\LazyJsonPages\Services\ClientFactory.
Loading history...
133
    }
134
135
    /**
136
     * Add the given option to the Guzzle client configuration.
137
     *
138
     * @param RequestOptions::* $name
0 ignored issues
show
Documentation Bug introduced by
The doc comment $name at position 0 could not be parsed: Unknown type name '$name' at position 0 in $name.
Loading history...
139
     */
140 3
    public function config(string $name, mixed $value): self
141
    {
142 3
        $this->config[$name] = $value;
143
144 3
        return $this;
145
    }
146
147
    /**
148
     * Add the given Guzzle client middleware.
149
     */
150 2
    public function middleware(string $name, callable $middleware): self
151
    {
152 2
        $this->middleware[$name] = $middleware;
153
154 2
        return $this;
155
    }
156
157
    /**
158
     * Add the given callback to handle the sending request.
159
     */
160 2
    public function onRequest(Closure $callback): self
161
    {
162 2
        $this->middleware['lazy_json_pages_tap'] ??= new Tap($this->tapCallbacks);
163
164 2
        $this->tapCallbacks->onRequest($callback);
165
166 2
        return $this;
167
    }
168
169
    /**
170
     * Add the given callback to handle the received response.
171
     */
172 2
    public function onResponse(Closure $callback): self
173
    {
174 2
        $this->middleware['lazy_json_pages_tap'] ??= new Tap($this->tapCallbacks);
175
176 2
        $this->tapCallbacks->onResponse($callback);
177
178 2
        return $this;
179
    }
180
181
    /**
182
     * Add the given callback to handle a transaction error.
183
     */
184 1
    public function onError(Closure $callback): self
185
    {
186 1
        $this->middleware['lazy_json_pages_tap'] ??= new Tap($this->tapCallbacks);
187
188 1
        $this->tapCallbacks->onError($callback);
189
190 1
        return $this;
191
    }
192
193
    /**
194
     * Throttle the requests to respect rate limiting.
195
     */
196 1
    public function throttle(int $requests, int $perSeconds): self
197
    {
198 1
        $this->rateLimits->add($requests, $perSeconds);
199
200 1
        $this->middleware['lazy_json_pages_throttle'] ??= Tap::once(fn() => $this->rateLimits->hit());
201
202 1
        return $this;
203
    }
204
205
    /**
206
     * Retrieve a configured Guzzle client instance.
207
     */
208 50
    public function make(): Client
209
    {
210 50
        $config = array_replace_recursive(self::$defaultConfig, $this->config);
211 50
        $config['handler'] ??= HandlerStack::create();
212
213 50
        foreach ([...self::$globalMiddleware, ...$this->middleware] as $name => $middleware) {
214 6
            $config['handler']->push($middleware, $name);
215
        }
216
217 50
        return new Client($config);
218
    }
219
}
220