1: <?php declare(strict_types=1);
2:
3: namespace Salient\Curler\Pager;
4:
5: use Psr\Http\Message\RequestInterface;
6: use Salient\Contract\Curler\CurlerInterface;
7: use Salient\Contract\Curler\CurlerPageInterface;
8: use Salient\Contract\Curler\CurlerPagerInterface;
9: use Salient\Contract\Http\HttpHeader;
10: use Salient\Contract\Http\HttpResponseInterface;
11: use Salient\Curler\CurlerPage;
12: use Salient\Curler\CurlerPageRequest;
13: use Salient\Http\HttpUtil;
14: use Salient\Http\Uri;
15: use Closure;
16:
17: /**
18: * Follows "Link" headers with rel="next" in responses from the endpoint
19: *
20: * @api
21: */
22: final class LinkPager implements CurlerPagerInterface
23: {
24: use HasEntitySelector;
25:
26: private ?int $PageSize;
27: private string $PageSizeKey;
28:
29: /**
30: * Creates a new LinkPager object
31: *
32: * @param (Closure(mixed): list<mixed>)|array-key|null $entitySelector Entities
33: * are returned from:
34: * - `$entitySelector($data)` if `$entitySelector` is a closure
35: * - `Arr::get($data, $entitySelector)` if `$entitySelector` is a string or
36: * integer, or
37: * - `$data` if `$entitySelector` is `null`
38: */
39: public function __construct(
40: ?int $pageSize = null,
41: $entitySelector = null,
42: string $pageSizeKey = 'per_page'
43: ) {
44: $this->PageSize = $pageSize;
45: if ($pageSize !== null) {
46: $this->PageSizeKey = $pageSizeKey;
47: }
48: $this->applyEntitySelector($entitySelector);
49: }
50:
51: /**
52: * @inheritDoc
53: */
54: public function getFirstRequest(
55: RequestInterface $request,
56: CurlerInterface $curler,
57: ?array $query = null
58: ) {
59: if ($this->PageSize === null) {
60: return $request;
61: }
62:
63: $query[$this->PageSizeKey] = $this->PageSize;
64: return new CurlerPageRequest(
65: $curler->replaceQuery($request, $query),
66: $query,
67: );
68: }
69:
70: /**
71: * @inheritDoc
72: */
73: public function getPage(
74: $data,
75: RequestInterface $request,
76: HttpResponseInterface $response,
77: CurlerInterface $curler,
78: ?CurlerPageInterface $previousPage = null,
79: ?array $query = null
80: ): CurlerPageInterface {
81: $data = ($this->EntitySelector)($data);
82:
83: foreach ($response->getHeaderValues(HttpHeader::LINK) as $link) {
84: /** @var array{string,rel?:string} */
85: $link = HttpUtil::getParameters($link);
86: if (($link['rel'] ?? null) === 'next') {
87: $link = trim($link[0], '<>');
88: $uri = $request->getUri();
89: $uri = Uri::from($uri)->follow($link);
90: $nextRequest = $request->withUri($uri);
91: break;
92: }
93: }
94:
95: return new CurlerPage($data, $nextRequest ?? null);
96: }
97: }
98: