Skip to content

Pagination

List methods return a page object rather than a plain array. Both page types implement \Countable and \IteratorAggregate, so a foreach over the result walks every page automatically, fetching further pages lazily under the client throttle.

CursorPage

Pennylane\Sdk\Pagination\CursorPage (generic over the item type T). Used by every list endpoint of the Company API, and most of the Firm API.

The API cursor does not encode filter or sort parameters, so nextPage() re-sends the original parameters together with the new cursor.

Public surface

Member Type Description
items list<T> Items on this page only.
hasMore bool Whether a further page exists.
nextCursor ?string Cursor to request the next page, or null on the last page.
included ?list<mixed> Side loaded records from the experimental include parameter, when the response carries an included key. null otherwise.
nextPage() ?CursorPage<T> Fetches the next page, or null on the last page (when hasMore is false or nextCursor is null).
iterPages() \Generator<int, CursorPage<T>> Iterates over the pages themselves, starting with this one.
getIterator() \Generator<int, T> Iterates over the items of every page, fetching pages lazily. This is what a foreach on the page calls.
count() int Number of items on THIS page only, not the total across all pages.

Iteration semantics

$page = $client->customerInvoices->list();

// Walks every page: fetches page 2, 3, ... lazily as the loop reaches the end of each page.
foreach ($page as $invoice) {
    echo $invoice->id . "\n";
}

// A second foreach on the SAME $page object walks the items again from the
// first page: getIterator() is a fresh generator each time it is called, and
// $page->items always holds the first page's items, so nothing is skipped or
// re-fetched from the network for that first page. Further pages ARE
// re-fetched over the network on each new iteration, since pages are not cached.
foreach ($page as $invoice) {
    echo $invoice->id . "\n";
}

For page level control, use items, nextPage() or iterPages() instead of the flattening foreach:

$page = $client->customerInvoices->list();
foreach ($page->iterPages() as $onePage) {
    echo 'Page with ' . $onePage->count() . " items, hasMore = " . var_export($onePage->hasMore, true) . "\n";
    foreach ($onePage->items as $invoice) {
        echo $invoice->id . "\n";
    }
}

NumberedPage

Pennylane\Sdk\Pagination\NumberedPage (generic over the item type T). Only three Firm API endpoints use this scheme: companies, fiscalYears and trialBalance. A response missing total_pages or current_page is treated as a single page.

Public surface

Member Type Description
items list<T> Items on this page only.
totalPages ?int Total number of pages, or null if the response omitted it.
currentPage ?int Current page number, or null if the response omitted it.
hasMore() bool true when both totalPages and currentPage are known and currentPage < totalPages.
nextPage() ?NumberedPage<T> Fetches the next page, or null when hasMore() is false.
iterPages() \Generator<int, NumberedPage<T>> Iterates over the pages themselves, starting with this one.
getIterator() \Generator<int, T> Iterates over the items of every page, fetching pages lazily.
count() int Number of items on THIS page only.

Note the asymmetry with CursorPage: hasMore is a property there, hasMore() is a method here, because it is derived from totalPages and currentPage rather than read directly off the response.

$firm = new Pennylane\Sdk\PennylaneFirm();

$page = $firm->companies->list();
foreach ($page as $company) {
    echo $company->id . "\n";
}