*/ final class StreamOf implements \IteratorAggregate { private ?ApiError $lastError = null; private ?PaginationDto $firstPagePagination = null; /** * @param \Closure(Pagination):(\Uwil\ArcadiaApiPhpClient\Lib\ApiDto\Dto\PageOf|ApiError) $pageFetcher */ public function __construct( private readonly \Closure $pageFetcher, private readonly PageSize $pageSize = new PageSize(), ) { } /** * Iterate over individual items of type T. * * @return \Generator */ public function getIterator(): \Generator { foreach ($this->pages() as $page) { foreach ($page->items() as $item) { yield $item; } } return $this->lastError; } /** * Iterate page by page (useful for batching operations). * * @return \Generator, mixed, ?ApiError> */ public function pages(): \Generator { $this->lastError = null; $pageNumber = 1; do { $pagination = Pagination::ofPage(new PageNumber($pageNumber), $this->pageSize); $result = ($this->pageFetcher)($pagination); if ($result instanceof ApiError) { $this->lastError = $result; return $result; } if ($this->firstPagePagination === null) { $this->firstPagePagination = $result->pagination(); } yield $result; if (! $result->pagination()->hasNextPage()) { break; } $pageNumber++; } while (true); return null; } /** * Execute a callback on each item. * Stops and returns the ApiError if an error occurs, or stops if the callback returns false. * * @param callable(T): (bool|void) $callback */ public function each(callable $callback): ?ApiError { foreach ($this->getIterator() as $item) { if ($callback($item) === false) { break; } } return $this->lastError; } /** * Yield at most $limit items without requesting subsequent pages unnecessarily. * * @return \Generator */ public function take(int $limit): \Generator { if ($limit <= 0) { return; } $count = 0; foreach ($this->getIterator() as $item) { yield $item; $count++; if ($count >= $limit) { break; } } } /** * Get the first item of the stream, or null if empty, or ApiError on failure. * * @return T|ApiError|null */ public function first(): mixed { foreach ($this->take(1) as $item) { return $item; } return $this->lastError; } public function lastError(): ?ApiError { return $this->lastError; } public function hasError(): bool { return $this->lastError !== null; } /** * Total number of items reported by the API (available once at least one page is fetched). */ public function totalCount(): ?int { return $this->firstPagePagination?->totalItems; } /** * Total number of pages reported by the API (available once at least one page is fetched). */ public function totalPages(): ?int { return $this->firstPagePagination?->totalPages; } }