1: | <?php declare(strict_types=1); |
2: | |
3: | namespace Salient\Contract\Curler; |
4: | |
5: | use Psr\Http\Client\ClientInterface; |
6: | use Psr\Http\Message\RequestInterface; |
7: | use Psr\Http\Message\ResponseInterface; |
8: | use Psr\Http\Message\UriInterface as PsrUriInterface; |
9: | use Salient\Contract\Cache\CacheInterface; |
10: | use Salient\Contract\Core\DateFormatterInterface; |
11: | use Salient\Contract\Core\Immutable; |
12: | use Salient\Contract\Http\AccessTokenInterface; |
13: | use Salient\Contract\Http\FormDataFlag; |
14: | use Salient\Contract\Http\HttpHeader; |
15: | use Salient\Contract\Http\HttpHeaderGroup; |
16: | use Salient\Contract\Http\HttpHeadersInterface; |
17: | use Salient\Contract\Http\HttpRequestHandlerInterface; |
18: | use Salient\Contract\Http\HttpResponseInterface; |
19: | use Salient\Contract\Http\UriInterface; |
20: | use Closure; |
21: | use OutOfRangeException; |
22: | use Stringable; |
23: | |
24: | /** |
25: | * @api |
26: | */ |
27: | interface CurlerInterface extends ClientInterface, Immutable |
28: | { |
29: | /** |
30: | * Get the URI of the endpoint |
31: | */ |
32: | public function getUri(): UriInterface; |
33: | |
34: | /** |
35: | * Get an instance with the given endpoint URI |
36: | * |
37: | * An exception is thrown if the endpoint URI has a query or fragment. |
38: | * |
39: | * @param PsrUriInterface|Stringable|string|null $uri |
40: | * @return static |
41: | */ |
42: | public function withUri($uri); |
43: | |
44: | /** |
45: | * Apply the URI and headers of the given request to a copy of the instance |
46: | * |
47: | * @return static |
48: | */ |
49: | public function withRequest(RequestInterface $request); |
50: | |
51: | /** |
52: | * Get the last request sent to the endpoint or passed to middleware |
53: | */ |
54: | public function getLastRequest(): ?RequestInterface; |
55: | |
56: | /** |
57: | * Get the last response received from the endpoint or returned by |
58: | * middleware |
59: | */ |
60: | public function getLastResponse(): ?HttpResponseInterface; |
61: | |
62: | /** |
63: | * Check if the last response contains JSON-encoded data |
64: | * |
65: | * @throws OutOfRangeException if no response has been received from the |
66: | * endpoint or returned by middleware. |
67: | */ |
68: | public function lastResponseIsJson(): bool; |
69: | |
70: | // -- |
71: | |
72: | /** |
73: | * Send a HEAD request to the endpoint |
74: | * |
75: | * @param mixed[]|null $query |
76: | */ |
77: | public function head(?array $query = null): HttpHeadersInterface; |
78: | |
79: | /** |
80: | * Send a GET request to the endpoint and return the body of the response |
81: | * |
82: | * @param mixed[]|null $query |
83: | * @return mixed |
84: | */ |
85: | public function get(?array $query = null); |
86: | |
87: | /** |
88: | * Send a POST request to the endpoint and return the body of the response |
89: | * |
90: | * @param mixed[]|object|null $data |
91: | * @param mixed[]|null $query |
92: | * @return mixed |
93: | */ |
94: | public function post($data = null, ?array $query = null); |
95: | |
96: | /** |
97: | * Send a PUT request to the endpoint and return the body of the response |
98: | * |
99: | * @param mixed[]|object|null $data |
100: | * @param mixed[]|null $query |
101: | * @return mixed |
102: | */ |
103: | public function put($data = null, ?array $query = null); |
104: | |
105: | /** |
106: | * Send a PATCH request to the endpoint and return the body of the response |
107: | * |
108: | * @param mixed[]|object|null $data |
109: | * @param mixed[]|null $query |
110: | * @return mixed |
111: | */ |
112: | public function patch($data = null, ?array $query = null); |
113: | |
114: | /** |
115: | * Send a DELETE request to the endpoint and return the body of the response |
116: | * |
117: | * @param mixed[]|object|null $data |
118: | * @param mixed[]|null $query |
119: | * @return mixed |
120: | */ |
121: | public function delete($data = null, ?array $query = null); |
122: | |
123: | // -- |
124: | |
125: | /** |
126: | * Send a GET request to the endpoint and iterate over response pages |
127: | * |
128: | * @param mixed[]|null $query |
129: | * @return iterable<mixed> |
130: | */ |
131: | public function getP(?array $query = null): iterable; |
132: | |
133: | /** |
134: | * Send a POST request to the endpoint and iterate over response pages |
135: | * |
136: | * @param mixed[]|object|null $data |
137: | * @param mixed[]|null $query |
138: | * @return iterable<mixed> |
139: | */ |
140: | public function postP($data = null, ?array $query = null): iterable; |
141: | |
142: | /** |
143: | * Send a PUT request to the endpoint and iterate over response pages |
144: | * |
145: | * @param mixed[]|object|null $data |
146: | * @param mixed[]|null $query |
147: | * @return iterable<mixed> |
148: | */ |
149: | public function putP($data = null, ?array $query = null): iterable; |
150: | |
151: | /** |
152: | * Send a PATCH request to the endpoint and iterate over response pages |
153: | * |
154: | * @param mixed[]|object|null $data |
155: | * @param mixed[]|null $query |
156: | * @return iterable<mixed> |
157: | */ |
158: | public function patchP($data = null, ?array $query = null): iterable; |
159: | |
160: | /** |
161: | * Send a DELETE request to the endpoint and iterate over response pages |
162: | * |
163: | * @param mixed[]|object|null $data |
164: | * @param mixed[]|null $query |
165: | * @return iterable<mixed> |
166: | */ |
167: | public function deleteP($data = null, ?array $query = null): iterable; |
168: | |
169: | // -- |
170: | |
171: | /** |
172: | * Send raw data to the endpoint in a POST request and return the body of |
173: | * the response |
174: | * |
175: | * @param mixed[]|null $query |
176: | * @return mixed |
177: | */ |
178: | public function postR(string $data, string $mediaType, ?array $query = null); |
179: | |
180: | /** |
181: | * Send raw data to the endpoint in a PUT request and return the body of the |
182: | * response |
183: | * |
184: | * @param mixed[]|null $query |
185: | * @return mixed |
186: | */ |
187: | public function putR(string $data, string $mediaType, ?array $query = null); |
188: | |
189: | /** |
190: | * Send raw data to the endpoint in a PATCH request and return the body of |
191: | * the response |
192: | * |
193: | * @param mixed[]|null $query |
194: | * @return mixed |
195: | */ |
196: | public function patchR(string $data, string $mediaType, ?array $query = null); |
197: | |
198: | /** |
199: | * Send raw data to the endpoint in a DELETE request and return the body of |
200: | * the response |
201: | * |
202: | * @param mixed[]|null $query |
203: | * @return mixed |
204: | */ |
205: | public function deleteR(string $data, string $mediaType, ?array $query = null); |
206: | |
207: | // -- |
208: | |
209: | /** |
210: | * Invalidate cached cookies |
211: | * |
212: | * Calling this method has no effect if the instance does not handle |
213: | * cookies. |
214: | * |
215: | * @return $this |
216: | */ |
217: | public function flushCookies(); |
218: | |
219: | // -- |
220: | |
221: | /** |
222: | * Get request headers |
223: | */ |
224: | public function getHttpHeaders(): HttpHeadersInterface; |
225: | |
226: | /** |
227: | * Get request headers that are not considered sensitive |
228: | */ |
229: | public function getPublicHttpHeaders(): HttpHeadersInterface; |
230: | |
231: | /** |
232: | * Get an array that maps request header names to values |
233: | * |
234: | * @return array<string,string[]> |
235: | */ |
236: | public function getHeaders(): array; |
237: | |
238: | /** |
239: | * Check if a request header exists |
240: | */ |
241: | public function hasHeader(string $name): bool; |
242: | |
243: | /** |
244: | * Get the value of a request header as a list of values |
245: | * |
246: | * @return string[] |
247: | */ |
248: | public function getHeader(string $name): array; |
249: | |
250: | /** |
251: | * Get the value of a request header as a list of values, splitting any |
252: | * comma-separated values |
253: | * |
254: | * @return string[] |
255: | */ |
256: | public function getHeaderValues(string $name): array; |
257: | |
258: | /** |
259: | * Get the comma-separated values of a request header |
260: | */ |
261: | public function getHeaderLine(string $name): string; |
262: | |
263: | /** |
264: | * Get the first value of a request header after splitting any |
265: | * comma-separated values |
266: | */ |
267: | public function getFirstHeaderLine(string $name): string; |
268: | |
269: | /** |
270: | * Get the last value of a request header after splitting any |
271: | * comma-separated values |
272: | */ |
273: | public function getLastHeaderLine(string $name): string; |
274: | |
275: | /** |
276: | * Get the only value of a request header after splitting any |
277: | * comma-separated values |
278: | * |
279: | * An exception is thrown if the request header has more than one value. |
280: | */ |
281: | public function getOneHeaderLine(string $name, bool $orSame = false): string; |
282: | |
283: | /** |
284: | * Get an instance with a value applied to a request header, replacing any |
285: | * existing values |
286: | * |
287: | * @param string[]|string $value |
288: | * @return static |
289: | */ |
290: | public function withHeader(string $name, $value); |
291: | |
292: | /** |
293: | * Get an instance with a value applied to a request header, preserving any |
294: | * existing values |
295: | * |
296: | * @param string[]|string $value |
297: | * @return static |
298: | */ |
299: | public function withAddedHeader(string $name, $value); |
300: | |
301: | /** |
302: | * Get an instance with a request header removed |
303: | * |
304: | * @return static |
305: | */ |
306: | public function withoutHeader(string $name); |
307: | |
308: | /** |
309: | * Check if the instance has an access token |
310: | */ |
311: | public function hasAccessToken(): bool; |
312: | |
313: | /** |
314: | * Get an instance that applies an access token to request headers |
315: | * |
316: | * @return static |
317: | */ |
318: | public function withAccessToken( |
319: | ?AccessTokenInterface $token, |
320: | string $headerName = HttpHeader::AUTHORIZATION |
321: | ); |
322: | |
323: | /** |
324: | * Check if a header is considered sensitive |
325: | */ |
326: | public function isSensitiveHeader(string $name): bool; |
327: | |
328: | /** |
329: | * Get an instance that treats a header as sensitive |
330: | * |
331: | * Headers in {@see HttpHeaderGroup::SENSITIVE} are considered sensitive by |
332: | * default. |
333: | * |
334: | * @return static |
335: | */ |
336: | public function withSensitiveHeader(string $name); |
337: | |
338: | /** |
339: | * Get an instance that does not treat the given header as sensitive |
340: | * |
341: | * @return static |
342: | */ |
343: | public function withoutSensitiveHeader(string $name); |
344: | |
345: | /** |
346: | * Get the media type applied to request headers |
347: | */ |
348: | public function getMediaType(): ?string; |
349: | |
350: | /** |
351: | * Get an instance that applies the given media type to request headers |
352: | * |
353: | * If `$type` is `null` (the default), `Content-Type` headers are |
354: | * automatically applied to requests as needed. |
355: | * |
356: | * @return static |
357: | */ |
358: | public function withMediaType(?string $type); |
359: | |
360: | /** |
361: | * Get the current user agent string |
362: | */ |
363: | public function getUserAgent(): string; |
364: | |
365: | /** |
366: | * Get an instance with the given user agent string |
367: | * |
368: | * If `$userAgent` is `null`, the default user agent string is restored. |
369: | * |
370: | * @return static |
371: | */ |
372: | public function withUserAgent(?string $userAgent); |
373: | |
374: | /** |
375: | * Check if the instance explicitly accepts JSON-encoded responses and |
376: | * assumes responses with no content type contain JSON |
377: | */ |
378: | public function expectsJson(): bool; |
379: | |
380: | /** |
381: | * Get an instance that explicitly accepts JSON-encoded responses and |
382: | * assumes responses with no content type contain JSON |
383: | * |
384: | * @return static |
385: | */ |
386: | public function withExpectJson(bool $expectJson = true); |
387: | |
388: | /** |
389: | * Check if the instance uses JSON to encode POST/PUT/PATCH/DELETE data |
390: | */ |
391: | public function postsJson(): bool; |
392: | |
393: | /** |
394: | * Get an instance that uses JSON to encode POST/PUT/PATCH/DELETE data |
395: | * |
396: | * @return static |
397: | */ |
398: | public function withPostJson(bool $postJson = true); |
399: | |
400: | /** |
401: | * Get the date formatter applied to the instance |
402: | */ |
403: | public function getDateFormatter(): ?DateFormatterInterface; |
404: | |
405: | /** |
406: | * Get an instance that uses the given date formatter to format and parse |
407: | * the endpoint's date and time values |
408: | * |
409: | * @return static |
410: | */ |
411: | public function withDateFormatter(?DateFormatterInterface $formatter); |
412: | |
413: | /** |
414: | * Get form data flags applied to the instance |
415: | * |
416: | * @return int-mask-of<FormDataFlag::*> |
417: | */ |
418: | public function getFormDataFlags(): int; |
419: | |
420: | /** |
421: | * Get an instance with the given form data flags |
422: | * |
423: | * Form data flags are used to encode data for query strings and message |
424: | * bodies. |
425: | * |
426: | * {@see FormDataFlag::PRESERVE_NUMERIC_KEYS} and |
427: | * {@see FormDataFlag::PRESERVE_STRING_KEYS} are applied by default. |
428: | * |
429: | * @param int-mask-of<FormDataFlag::*> $flags |
430: | * @return static |
431: | */ |
432: | public function withFormDataFlags(int $flags); |
433: | |
434: | /** |
435: | * Get an instance with the given json_decode() flags |
436: | * |
437: | * {@see \JSON_OBJECT_AS_ARRAY} is applied by default. |
438: | * {@see \JSON_THROW_ON_ERROR} is always applied and cannot be disabled. |
439: | * |
440: | * @param int-mask-of<\JSON_BIGINT_AS_STRING|\JSON_INVALID_UTF8_IGNORE|\JSON_INVALID_UTF8_SUBSTITUTE|\JSON_OBJECT_AS_ARRAY|\JSON_THROW_ON_ERROR> $flags |
441: | * @return static |
442: | */ |
443: | public function withJsonDecodeFlags(int $flags); |
444: | |
445: | /** |
446: | * Get an instance with the given middleware applied to the request handler |
447: | * stack |
448: | * |
449: | * @param CurlerMiddlewareInterface|HttpRequestHandlerInterface|Closure(RequestInterface $request, Closure(RequestInterface): HttpResponseInterface $next, CurlerInterface $curler): ResponseInterface $middleware |
450: | * @return static |
451: | */ |
452: | public function withMiddleware($middleware, ?string $name = null); |
453: | |
454: | /** |
455: | * Get an instance where the given middleware is not applied to requests |
456: | * |
457: | * @param CurlerMiddlewareInterface|HttpRequestHandlerInterface|Closure|string $middleware |
458: | * @return static |
459: | */ |
460: | public function withoutMiddleware($middleware); |
461: | |
462: | /** |
463: | * Get the endpoint's pagination handler |
464: | */ |
465: | public function getPager(): ?CurlerPagerInterface; |
466: | |
467: | /** |
468: | * Check if the endpoint's pagination handler is used to process every |
469: | * request |
470: | */ |
471: | public function alwaysPaginates(): bool; |
472: | |
473: | /** |
474: | * Get an instance with the given pagination handler |
475: | * |
476: | * @param bool $alwaysPaginate If `true`, the pager is used to process |
477: | * requests even if no pagination is required. |
478: | * @return static |
479: | */ |
480: | public function withPager(?CurlerPagerInterface $pager, bool $alwaysPaginate = false); |
481: | |
482: | /** |
483: | * Get the endpoint's cache store |
484: | */ |
485: | public function getCacheStore(): ?CacheInterface; |
486: | |
487: | /** |
488: | * Get an instance with the given cache store |
489: | * |
490: | * If no `$store` is given, cookies and responses are cached in the default |
491: | * cache store as needed. |
492: | * |
493: | * @return static |
494: | */ |
495: | public function withCacheStore(?CacheInterface $store = null); |
496: | |
497: | /** |
498: | * Check if the instance handles cookies |
499: | */ |
500: | public function hasCookies(): bool; |
501: | |
502: | /** |
503: | * Get an instance that handles cookies |
504: | * |
505: | * @return static |
506: | */ |
507: | public function withCookies(?string $cacheKey = null); |
508: | |
509: | /** |
510: | * Get an instance that does not handle cookies |
511: | * |
512: | * @return static |
513: | */ |
514: | public function withoutCookies(); |
515: | |
516: | /** |
517: | * Check if response caching is enabled |
518: | */ |
519: | public function hasResponseCache(): bool; |
520: | |
521: | /** |
522: | * Get an instance that caches responses to GET and HEAD requests |
523: | * |
524: | * HTTP caching headers are ignored. USE RESPONSIBLY. |
525: | * |
526: | * @return static |
527: | */ |
528: | public function withResponseCache(bool $cacheResponses = true); |
529: | |
530: | /** |
531: | * Check if POST response caching is enabled |
532: | */ |
533: | public function hasPostResponseCache(): bool; |
534: | |
535: | /** |
536: | * Get an instance that caches responses to repeatable POST requests |
537: | * |
538: | * {@see withResponseCache()} must also be called to enable caching. |
539: | * |
540: | * @return static |
541: | */ |
542: | public function withPostResponseCache(bool $cachePostResponses = true); |
543: | |
544: | /** |
545: | * Get an instance that uses a callback to generate response cache keys |
546: | * |
547: | * The callback's return value is hashed and combined with request method |
548: | * and URI to create a response cache key. |
549: | * |
550: | * @param (callable(RequestInterface $request, CurlerInterface $curler): (string[]|string))|null $callback |
551: | * @return static |
552: | */ |
553: | public function withCacheKeyCallback(?callable $callback); |
554: | |
555: | /** |
556: | * Get the lifetime of cached responses, in seconds |
557: | * |
558: | * @return int<-1,max> |
559: | */ |
560: | public function getCacheLifetime(): int; |
561: | |
562: | /** |
563: | * Get an instance where cached responses expire after the given number of |
564: | * seconds |
565: | * |
566: | * `3600` is applied by default. |
567: | * |
568: | * {@see withResponseCache()} must also be called to enable caching. |
569: | * |
570: | * @param int<-1,max> $seconds - `0`: cache responses indefinitely |
571: | * - `-1`: disable caching until the method is called again with `$seconds` |
572: | * greater than or equal to `0` |
573: | * @return static |
574: | */ |
575: | public function withCacheLifetime(int $seconds); |
576: | |
577: | /** |
578: | * Check if the instance replaces cached responses even if they haven't |
579: | * expired |
580: | */ |
581: | public function refreshesCache(): bool; |
582: | |
583: | /** |
584: | * Get an instance that replaces cached responses even if they haven't |
585: | * expired |
586: | * |
587: | * @return static |
588: | */ |
589: | public function withRefreshCache(bool $refresh = true); |
590: | |
591: | /** |
592: | * Get the connection timeout applied to the instance, in seconds |
593: | * |
594: | * @return int<0,max>|null |
595: | */ |
596: | public function getTimeout(): ?int; |
597: | |
598: | /** |
599: | * Get an instance with the given connection timeout |
600: | * |
601: | * @param int<0,max>|null $seconds - `0`: wait indefinitely |
602: | * - `null` (default): use the underlying client's default connection |
603: | * timeout |
604: | * @return static |
605: | */ |
606: | public function withTimeout(?int $seconds); |
607: | |
608: | /** |
609: | * Check if the instance follows "Location" headers |
610: | */ |
611: | public function followsRedirects(): bool; |
612: | |
613: | /** |
614: | * Get an instance that follows "Location" headers |
615: | * |
616: | * @return static |
617: | */ |
618: | public function withFollowRedirects(bool $follow = true); |
619: | |
620: | /** |
621: | * Get the maximum number of "Location" headers followed |
622: | * |
623: | * @return int<-1,max>|null |
624: | */ |
625: | public function getMaxRedirects(): ?int; |
626: | |
627: | /** |
628: | * Get an instance that limits the number of "Location" headers followed |
629: | * |
630: | * @param int<-1,max>|null $redirects - `-1`: allow unlimited redirects |
631: | * - `0`: disable redirects (same effect as `withFollowRedirects(false)`) |
632: | * - `null` (default): use the underlying client's default redirect limit |
633: | * @return static |
634: | */ |
635: | public function withMaxRedirects(?int $redirects); |
636: | |
637: | /** |
638: | * Check if the instance retries throttled requests when the endpoint |
639: | * returns a "Retry-After" header |
640: | */ |
641: | public function getRetryAfterTooManyRequests(): bool; |
642: | |
643: | /** |
644: | * Get the maximum delay between request attempts |
645: | * |
646: | * @return int<0,max> |
647: | */ |
648: | public function getRetryAfterMaxSeconds(): int; |
649: | |
650: | /** |
651: | * Get an instance that retries throttled requests when the endpoint returns |
652: | * a "Retry-After" header |
653: | * |
654: | * @return static |
655: | */ |
656: | public function withRetryAfterTooManyRequests(bool $retry = true); |
657: | |
658: | /** |
659: | * Get an instance that limits the delay between request attempts |
660: | * |
661: | * `300` is applied by default. |
662: | * |
663: | * @param int<0,max> $seconds If `0`, unlimited delays are allowed. |
664: | * @return static |
665: | */ |
666: | public function withRetryAfterMaxSeconds(int $seconds); |
667: | |
668: | /** |
669: | * Check if exceptions are thrown for HTTP errors |
670: | */ |
671: | public function throwsHttpErrors(): bool; |
672: | |
673: | /** |
674: | * Get an instance that throws exceptions for HTTP errors |
675: | * |
676: | * @return static |
677: | */ |
678: | public function withThrowHttpErrors(bool $throw = true); |
679: | |
680: | /** |
681: | * Use the form data flags and date formatter applied to the instance to |
682: | * replace the query string of a request or URI |
683: | * |
684: | * @template T of RequestInterface|PsrUriInterface|Stringable|string |
685: | * |
686: | * @param T $value |
687: | * @param mixed[] $query |
688: | * @return (T is RequestInterface|PsrUriInterface ? T : UriInterface) |
689: | */ |
690: | public function replaceQuery($value, array $query); |
691: | } |
692: |