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