| 
<?phpnamespace 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(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\promise_for($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
 */
 public function current()
 {
 return $this->valid() ? $this->result : false;
 }
 
 public function key()
 {
 return $this->valid() ? $this->requestCount - 1 : null;
 }
 
 public function next()
 {
 $this->result = null;
 }
 
 public function valid()
 {
 if ($this->result) {
 return true;
 }
 
 if ($this->nextToken || !$this->requestCount) {
 $this->result = $this->client->execute(
 $this->createNextCommand($this->args, $this->nextToken)
 );
 $this->nextToken = $this->determineNextToken($this->result);
 $this->requestCount++;
 return true;
 }
 
 return false;
 }
 
 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, $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));
 }
 }
 
 |