<?php /* * Copyright 2016 Google LLC * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ namespace Google\ApiCore; use Generator; use Google\Protobuf\Internal\Message; use Google\Protobuf\Internal\MapField; use IteratorAggregate; /** * A Page object wraps an API list method response and provides methods * to retrieve additional pages using the page token. */ class Page implements IteratorAggregate { const FINAL_PAGE_TOKEN = ""; private $call; private $callable; private $options; private $pageStreamingDescriptor; private $pageToken; // @phpstan-ignore-line private $response; /** * Page constructor. * * @param Call $call * @param array $options * @param callable $callable * @param PageStreamingDescriptor $pageStreamingDescriptor * @param Message $response */ public function __construct( Call $call, array $options, callable $callable, PageStreamingDescriptor $pageStreamingDescriptor, Message $response ) { $this->call = $call; $this->options = $options; $this->callable = $callable; $this->pageStreamingDescriptor = $pageStreamingDescriptor; $this->response = $response; $requestPageTokenGetMethod = $this->pageStreamingDescriptor->getRequestPageTokenGetMethod(); $this->pageToken = $this->call->getMessage()->$requestPageTokenGetMethod(); } /** * Returns true if there are more pages that can be retrieved from the * API. * * @return bool */ public function hasNextPage() { return strcmp($this->getNextPageToken(), Page::FINAL_PAGE_TOKEN) != 0; } /** * Returns the next page token from the response. * * @return string */ public function getNextPageToken() { $responsePageTokenGetMethod = $this->pageStreamingDescriptor->getResponsePageTokenGetMethod(); return $this->getResponseObject()->$responsePageTokenGetMethod(); } /** * Retrieves the next Page object using the next page token. * * @param int|null $pageSize * @throws ValidationException if there are no pages remaining, or if pageSize is supplied but * is not supported by the API * @throws ApiException if the call to fetch the next page fails. * @return Page */ public function getNextPage($pageSize = null) { if (!$this->hasNextPage()) { throw new ValidationException( 'Could not complete getNextPage operation: ' . 'there are no more pages to retrieve.' ); } $oldRequest = $this->getRequestObject(); $requestClass = get_class($oldRequest); $newRequest = new $requestClass(); $newRequest->mergeFrom($oldRequest); $requestPageTokenSetMethod = $this->pageStreamingDescriptor->getRequestPageTokenSetMethod(); $newRequest->$requestPageTokenSetMethod($this->getNextPageToken()); if (isset($pageSize)) { if (!$this->pageStreamingDescriptor->requestHasPageSizeField()) { throw new ValidationException( 'pageSize argument was defined, but the method does not ' . 'support a page size parameter in the optional array argument' ); } $requestPageSizeSetMethod = $this->pageStreamingDescriptor->getRequestPageSizeSetMethod(); $newRequest->$requestPageSizeSetMethod($pageSize); } $this->call = $this->call->withMessage($newRequest); $callable = $this->callable; $response = $callable( $this->call, $this->options )->wait(); return new Page( $this->call, $this->options, $this->callable, $this->pageStreamingDescriptor, $response ); } /** * Return the number of elements in the response. * * @return int */ public function getPageElementCount() { $resourcesGetMethod = $this->pageStreamingDescriptor->getResourcesGetMethod(); return count($this->getResponseObject()->$resourcesGetMethod()); } /** * Return an iterator over the elements in the response. * * @return Generator */ #[\ReturnTypeWillChange] public function getIterator() { $resourcesGetMethod = $this->pageStreamingDescriptor->getResourcesGetMethod(); $items = $this->getResponseObject()->$resourcesGetMethod(); foreach ($items as $key => $element) { if ($items instanceof MapField) { yield $key => $element; } else { yield $element; } } } /** * Return an iterator over Page objects, beginning with this object. * Additional Page objects are retrieved lazily via API calls until * all elements have been retrieved. * * @return Generator|array<Page> * @throws ValidationException * @throws ApiException */ public function iteratePages() { $currentPage = $this; yield $this; while ($currentPage->hasNextPage()) { $currentPage = $currentPage->getNextPage(); yield $currentPage; } } /** * Gets the request object used to generate the Page. * * @return mixed|Message */ public function getRequestObject() { return $this->call->getMessage(); } /** * Gets the API response object. * * @return mixed|Message */ public function getResponseObject() { return $this->response; } /** * Returns a collection of elements with a fixed size set by * the collectionSize parameter. The collection will only contain * fewer than collectionSize elements if there are no more * pages to be retrieved from the server. * * NOTE: it is an error to call this method if an optional parameter * to set the page size is not supported or has not been set in the * API call that was used to create this page. It is also an error * if the collectionSize parameter is less than the page size that * has been set. * * @param int $collectionSize * @throws ValidationException if a FixedSizeCollection of the specified size cannot be constructed * @return FixedSizeCollection */ public function expandToFixedSizeCollection($collectionSize) { if (!$this->pageStreamingDescriptor->requestHasPageSizeField()) { throw new ValidationException( "FixedSizeCollection is not supported for this method, because " . "the method does not support an optional argument to set the " . "page size." ); } $request = $this->getRequestObject(); $pageSizeGetMethod = $this->pageStreamingDescriptor->getRequestPageSizeGetMethod(); $pageSize = $request->$pageSizeGetMethod(); if (is_null($pageSize)) { throw new ValidationException( "Error while expanding Page to FixedSizeCollection: No page size " . "parameter found. The page size parameter must be set in the API " . "optional arguments array, and must be less than the collectionSize " . "parameter, in order to create a FixedSizeCollection object." ); } if ($pageSize > $collectionSize) { throw new ValidationException( "Error while expanding Page to FixedSizeCollection: collectionSize " . "parameter is less than the page size optional argument specified in " . "the API call. collectionSize: $collectionSize, page size: $pageSize" ); } return new FixedSizeCollection($this, $collectionSize); } }