123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190 |
- <?php
- namespace Aws;
- use GuzzleHttp\Promise;
- /**
- * Iterator that yields each page of results of a pageable operation.
- */
- class ResultPaginator implements \Iterator
- {
- /** @var AwsClientInterface Client performing operations. */
- private $client;
- /** @var string Name of the operation being paginated. */
- private $operation;
- /** @var array Args for the operation. */
- private $args;
- /** @var array Configuration for the paginator. */
- private $config;
- /** @var Result Most recent result from the client. */
- private $result;
- /** @var string|array Next token to use for pagination. */
- private $nextToken;
- /** @var int Number of operations/requests performed. */
- private $requestCount = 0;
- /**
- * @param AwsClientInterface $client
- * @param string $operation
- * @param array $args
- * @param array $config
- */
- public function __construct(
- AwsClientInterface $client,
- $operation,
- array $args,
- array $config
- ) {
- $this->client = $client;
- $this->operation = $operation;
- $this->args = $args;
- $this->config = $config;
- }
- /**
- * Runs a paginator asynchronously and uses a callback to handle results.
- *
- * The callback should have the signature: function (Aws\Result $result).
- * A non-null return value from the callback will be yielded by the
- * promise. This means that you can return promises from the callback that
- * will need to be resolved before continuing iteration over the remaining
- * items, essentially merging in other promises to the iteration. The last
- * non-null value returned by the callback will be the result that fulfills
- * the promise to any downstream promises.
- *
- * @param callable $handleResult Callback for handling each page of results.
- * The callback accepts the result that was
- * yielded as a single argument. If the
- * callback returns a promise, the promise
- * will be merged into the coroutine.
- *
- * @return Promise\Promise
- */
- public function each(callable $handleResult)
- {
- return Promise\Coroutine::of(function () use ($handleResult) {
- $nextToken = null;
- do {
- $command = $this->createNextCommand($this->args, $nextToken);
- $result = (yield $this->client->executeAsync($command));
- $nextToken = $this->determineNextToken($result);
- $retVal = $handleResult($result);
- if ($retVal !== null) {
- yield Promise\Create::promiseFor($retVal);
- }
- } while ($nextToken);
- });
- }
- /**
- * Returns an iterator that iterates over the values of applying a JMESPath
- * search to each result yielded by the iterator as a flat sequence.
- *
- * @param string $expression JMESPath expression to apply to each result.
- *
- * @return \Iterator
- */
- public function search($expression)
- {
- // Apply JMESPath expression on each result, but as a flat sequence.
- return flatmap($this, function (Result $result) use ($expression) {
- return (array) $result->search($expression);
- });
- }
- /**
- * @return Result
- */
- #[\ReturnTypeWillChange]
- public function current()
- {
- return $this->valid() ? $this->result : false;
- }
- #[\ReturnTypeWillChange]
- public function key()
- {
- return $this->valid() ? $this->requestCount - 1 : null;
- }
- #[\ReturnTypeWillChange]
- public function next()
- {
- $this->result = null;
- }
- #[\ReturnTypeWillChange]
- public function valid()
- {
- if ($this->result) {
- return true;
- }
- if ($this->nextToken || !$this->requestCount) {
- //Forward/backward paging can result in a case where the last page's nextforwardtoken
- //is the same as the one that came before it. This can cause an infinite loop.
- $hasBidirectionalPaging = $this->config['output_token'] === 'nextForwardToken';
- if ($hasBidirectionalPaging && $this->nextToken) {
- $tokenKey = $this->config['input_token'];
- $previousToken = $this->nextToken[$tokenKey];
- }
- $this->result = $this->client->execute(
- $this->createNextCommand($this->args, $this->nextToken)
- );
- $this->nextToken = $this->determineNextToken($this->result);
- if (isset($previousToken)
- && $previousToken === $this->nextToken[$tokenKey]
- ) {
- return false;
- }
- $this->requestCount++;
- return true;
- }
- return false;
- }
- #[\ReturnTypeWillChange]
- public function rewind()
- {
- $this->requestCount = 0;
- $this->nextToken = null;
- $this->result = null;
- }
- private function createNextCommand(array $args, array $nextToken = null)
- {
- return $this->client->getCommand($this->operation, array_merge($args, ($nextToken ?: [])));
- }
- private function determineNextToken(Result $result)
- {
- if (!$this->config['output_token']) {
- return null;
- }
- if ($this->config['more_results']
- && !$result->search($this->config['more_results'])
- ) {
- return null;
- }
- $nextToken = is_scalar($this->config['output_token'])
- ? [$this->config['input_token'] => $this->config['output_token']]
- : array_combine($this->config['input_token'], $this->config['output_token']);
- return array_filter(array_map(function ($outputToken) use ($result) {
- return $result->search($outputToken);
- }, $nextToken));
- }
- }
|