1 | <?php |
||
2 | |||
3 | declare(strict_types=1); |
||
4 | |||
5 | namespace Cerbero\LazyJsonPages; |
||
6 | |||
7 | use Cerbero\LazyJsonPages\Data\Config; |
||
8 | use Cerbero\LazyJsonPages\Data\Dot; |
||
9 | use Cerbero\LazyJsonPages\Paginations\AnyPagination; |
||
10 | use Cerbero\LazyJsonPages\Services\ClientFactory; |
||
11 | use Cerbero\LazyJsonPages\Sources\AnySource; |
||
12 | use Closure; |
||
13 | use GuzzleHttp\RequestOptions; |
||
14 | use Illuminate\Support\LazyCollection; |
||
15 | use Psr\Http\Message\RequestInterface as Request; |
||
16 | use Psr\Http\Message\ResponseInterface as Response; |
||
17 | use Throwable; |
||
18 | |||
19 | /** |
||
20 | * The Lazy JSON Pages entry-point |
||
21 | */ |
||
22 | final class LazyJsonPages |
||
23 | { |
||
24 | /** |
||
25 | * The HTTP client factory. |
||
26 | */ |
||
27 | private readonly ClientFactory $client; |
||
28 | |||
29 | /** |
||
30 | * The raw configuration of the API pagination. |
||
31 | * |
||
32 | * @var array<Config::OPTION_*, mixed> |
||
0 ignored issues
–
show
Documentation
Bug
introduced
by
![]() |
|||
33 | */ |
||
34 | private array $config = []; |
||
35 | |||
36 | /** |
||
37 | * Add a global middleware. |
||
38 | */ |
||
39 | 2 | public static function globalMiddleware(string $name, callable $middleware): void |
|
40 | { |
||
41 | 2 | ClientFactory::globalMiddleware($name, $middleware); |
|
42 | } |
||
43 | |||
44 | /** |
||
45 | * Instantiate the class statically. |
||
46 | */ |
||
47 | 50 | public static function from(mixed $source): self |
|
48 | { |
||
49 | 50 | return new self($source); |
|
50 | } |
||
51 | |||
52 | /** |
||
53 | * Instantiate the class. |
||
54 | */ |
||
55 | 50 | public function __construct(private readonly mixed $source) |
|
56 | { |
||
57 | 50 | $this->client = new ClientFactory(); |
|
58 | } |
||
59 | |||
60 | /** |
||
61 | * Set the name of the page. |
||
62 | */ |
||
63 | 1 | public function pageName(string $name): self |
|
64 | { |
||
65 | 1 | $this->config[Config::OPTION_PAGE_NAME] = $name; |
|
66 | |||
67 | 1 | return $this; |
|
68 | } |
||
69 | |||
70 | /** |
||
71 | * Set the pattern to capture the page in the URI path. |
||
72 | */ |
||
73 | 4 | public function pageInPath(string $pattern = '/(\d+)(?!.*\d)/'): self |
|
74 | { |
||
75 | 4 | $this->config[Config::OPTION_PAGE_IN_PATH] = $pattern; |
|
76 | |||
77 | 4 | return $this; |
|
78 | } |
||
79 | |||
80 | /** |
||
81 | * Set the number of the first page. |
||
82 | */ |
||
83 | 10 | public function firstPage(int $page): self |
|
84 | { |
||
85 | 10 | $this->config[Config::OPTION_FIRST_PAGE] = max(0, $page); |
|
86 | |||
87 | 10 | return $this; |
|
88 | } |
||
89 | |||
90 | /** |
||
91 | * Set the total number of pages. |
||
92 | */ |
||
93 | 33 | public function totalPages(string $key): self |
|
94 | { |
||
95 | 33 | $this->config[Config::OPTION_TOTAL_PAGES_KEY] = $key; |
|
96 | |||
97 | 33 | return $this; |
|
98 | } |
||
99 | |||
100 | /** |
||
101 | * Set the total number of items. |
||
102 | */ |
||
103 | 4 | public function totalItems(string $key): self |
|
104 | { |
||
105 | 4 | $this->config[Config::OPTION_TOTAL_ITEMS_KEY] = $key; |
|
106 | |||
107 | 4 | return $this; |
|
108 | } |
||
109 | |||
110 | /** |
||
111 | * Set the number of the last page. |
||
112 | */ |
||
113 | 5 | public function lastPage(string $key): self |
|
114 | { |
||
115 | 5 | $this->config[Config::OPTION_LAST_PAGE_KEY] = $key; |
|
116 | |||
117 | 5 | return $this; |
|
118 | } |
||
119 | |||
120 | /** |
||
121 | * Set the cursor or next page. |
||
122 | */ |
||
123 | 2 | public function cursor(string $key): self |
|
124 | { |
||
125 | 2 | $this->config[Config::OPTION_CURSOR_KEY] = $key; |
|
126 | |||
127 | 2 | return $this; |
|
128 | } |
||
129 | |||
130 | /** |
||
131 | * Set the offset. |
||
132 | */ |
||
133 | 4 | public function offset(string $key = 'offset'): self |
|
134 | { |
||
135 | 4 | $this->config[Config::OPTION_OFFSET_KEY] = $key; |
|
136 | |||
137 | 4 | return $this; |
|
138 | } |
||
139 | |||
140 | /** |
||
141 | * Set the Link header pagination. |
||
142 | */ |
||
143 | 4 | public function linkHeader(): self |
|
144 | { |
||
145 | 4 | $this->config[Config::OPTION_HAS_LINK_HEADER] = true; |
|
146 | |||
147 | 4 | return $this; |
|
148 | } |
||
149 | |||
150 | /** |
||
151 | * Set the custom pagination. |
||
152 | */ |
||
153 | 5 | public function pagination(string $class): self |
|
154 | { |
||
155 | 5 | $this->config[Config::OPTION_PAGINATION] = $class; |
|
156 | |||
157 | 5 | return $this; |
|
158 | } |
||
159 | |||
160 | /** |
||
161 | * Throttle the requests to respect rate limiting. |
||
162 | */ |
||
163 | 1 | public function throttle(int $requests, int $perSeconds = 0, int $perMinutes = 0, int $perHours = 0): self |
|
164 | { |
||
165 | 1 | $seconds = $perSeconds + $perMinutes * 60 + $perHours * 3600; |
|
166 | |||
167 | 1 | if ($requests > 0 && $seconds > 0) { |
|
168 | 1 | $this->config[Config::OPTION_RATE_LIMITS] ??= $this->client->rateLimits; |
|
169 | |||
170 | 1 | $this->client->throttle($requests, $seconds); |
|
171 | } |
||
172 | |||
173 | 1 | return $this; |
|
174 | } |
||
175 | |||
176 | /** |
||
177 | * Set the maximum number of concurrent async HTTP requests. |
||
178 | */ |
||
179 | 2 | public function async(int $requests): self |
|
180 | { |
||
181 | 2 | $this->config[Config::OPTION_ASYNC] = max(1, $requests); |
|
182 | |||
183 | 2 | $this->client->config(RequestOptions::STREAM, $this->config[Config::OPTION_ASYNC] === 1); |
|
184 | |||
185 | 2 | return $this; |
|
186 | } |
||
187 | |||
188 | /** |
||
189 | * Set the server connection timeout in seconds. |
||
190 | */ |
||
191 | 1 | public function connectionTimeout(float|int $seconds): self |
|
192 | { |
||
193 | 1 | $this->client->config(RequestOptions::CONNECT_TIMEOUT, max(0, $seconds)); |
|
194 | |||
195 | 1 | $this->client->config(RequestOptions::READ_TIMEOUT, max(0, $seconds)); |
|
196 | |||
197 | 1 | return $this; |
|
198 | } |
||
199 | |||
200 | /** |
||
201 | * Set an HTTP request timeout in seconds. |
||
202 | */ |
||
203 | 1 | public function requestTimeout(float|int $seconds): self |
|
204 | { |
||
205 | 1 | $this->client->config(RequestOptions::TIMEOUT, max(0, $seconds)); |
|
206 | |||
207 | 1 | return $this; |
|
208 | } |
||
209 | |||
210 | /** |
||
211 | * Set the number of attempts to fetch pages. |
||
212 | */ |
||
213 | 1 | public function attempts(int $times): self |
|
214 | { |
||
215 | 1 | $this->config[Config::OPTION_ATTEMPTS] = max(1, $times); |
|
216 | |||
217 | 1 | return $this; |
|
218 | } |
||
219 | |||
220 | /** |
||
221 | * Set the backoff strategy. |
||
222 | */ |
||
223 | 1 | public function backoff(Closure $callback): self |
|
224 | { |
||
225 | 1 | $this->config[Config::OPTION_BACKOFF] = $callback; |
|
226 | |||
227 | 1 | return $this; |
|
228 | } |
||
229 | |||
230 | /** |
||
231 | * Add an HTTP client middleware. |
||
232 | */ |
||
233 | 2 | public function middleware(string $name, callable $middleware): self |
|
234 | { |
||
235 | 2 | $this->client->middleware($name, $middleware); |
|
236 | |||
237 | 2 | return $this; |
|
238 | } |
||
239 | |||
240 | /** |
||
241 | * Handle the sending request. |
||
242 | * |
||
243 | * @param Closure(Request $request, array<string, mixed> $config): void $callback |
||
244 | */ |
||
245 | 2 | public function onRequest(Closure $callback): self |
|
246 | { |
||
247 | 2 | $this->client->onRequest($callback); |
|
248 | |||
249 | 2 | return $this; |
|
250 | } |
||
251 | |||
252 | /** |
||
253 | * Handle the received response. |
||
254 | * |
||
255 | * @param Closure(Response $response, Request $request, array<string, mixed> $config): void $callback |
||
256 | */ |
||
257 | 2 | public function onResponse(Closure $callback): self |
|
258 | { |
||
259 | 2 | $this->client->onResponse($callback); |
|
260 | |||
261 | 2 | return $this; |
|
262 | } |
||
263 | |||
264 | /** |
||
265 | * Handle a transaction error. |
||
266 | * |
||
267 | * @param Closure(Throwable $e, Request $request, ?Response $response, array<string, mixed> $config): void $callback |
||
268 | */ |
||
269 | 1 | public function onError(Closure $callback): self |
|
270 | { |
||
271 | 1 | $this->client->onError($callback); |
|
272 | |||
273 | 1 | return $this; |
|
274 | } |
||
275 | |||
276 | /** |
||
277 | * Retrieve a lazy collection yielding the paginated items. |
||
278 | * |
||
279 | * @return LazyCollection<int, mixed> |
||
280 | * @throws \Cerbero\LazyJsonPages\Exceptions\UnsupportedPaginationException |
||
281 | */ |
||
282 | 50 | public function collect(string $dot = '*'): LazyCollection |
|
283 | { |
||
284 | 50 | $this->config[Config::OPTION_ITEMS_POINTER] = (new Dot($dot))->toPointer(); |
|
285 | |||
286 | 50 | return new LazyCollection(function () { |
|
287 | 50 | $client = $this->client->make(); |
|
288 | 50 | $config = new Config(...$this->config); /** @phpstan-ignore-line */ |
|
289 | 50 | $source = (new AnySource($this->source))->setClient($client); |
|
290 | |||
291 | 50 | yield from new AnyPagination($source, $client, $config); |
|
292 | 50 | }); |
|
293 | } |
||
294 | } |
||
295 |