ResultPaginator.php 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. <?php
  2. namespace Aws;
  3. use GuzzleHttp\Promise;
  4. /**
  5. * Iterator that yields each page of results of a pageable operation.
  6. */
  7. class ResultPaginator implements \Iterator
  8. {
  9. /** @var AwsClientInterface Client performing operations. */
  10. private $client;
  11. /** @var string Name of the operation being paginated. */
  12. private $operation;
  13. /** @var array Args for the operation. */
  14. private $args;
  15. /** @var array Configuration for the paginator. */
  16. private $config;
  17. /** @var Result Most recent result from the client. */
  18. private $result;
  19. /** @var string|array Next token to use for pagination. */
  20. private $nextToken;
  21. /** @var int Number of operations/requests performed. */
  22. private $requestCount = 0;
  23. /**
  24. * @param AwsClientInterface $client
  25. * @param string $operation
  26. * @param array $args
  27. * @param array $config
  28. */
  29. public function __construct(
  30. AwsClientInterface $client,
  31. $operation,
  32. array $args,
  33. array $config
  34. ) {
  35. $this->client = $client;
  36. $this->operation = $operation;
  37. $this->args = $args;
  38. $this->config = $config;
  39. }
  40. /**
  41. * Runs a paginator asynchronously and uses a callback to handle results.
  42. *
  43. * The callback should have the signature: function (Aws\Result $result).
  44. * A non-null return value from the callback will be yielded by the
  45. * promise. This means that you can return promises from the callback that
  46. * will need to be resolved before continuing iteration over the remaining
  47. * items, essentially merging in other promises to the iteration. The last
  48. * non-null value returned by the callback will be the result that fulfills
  49. * the promise to any downstream promises.
  50. *
  51. * @param callable $handleResult Callback for handling each page of results.
  52. * The callback accepts the result that was
  53. * yielded as a single argument. If the
  54. * callback returns a promise, the promise
  55. * will be merged into the coroutine.
  56. *
  57. * @return Promise\Promise
  58. */
  59. public function each(callable $handleResult)
  60. {
  61. return Promise\coroutine(function () use ($handleResult) {
  62. $nextToken = null;
  63. do {
  64. $command = $this->createNextCommand($this->args, $nextToken);
  65. $result = (yield $this->client->executeAsync($command));
  66. $nextToken = $this->determineNextToken($result);
  67. $retVal = $handleResult($result);
  68. if ($retVal !== null) {
  69. yield Promise\promise_for($retVal);
  70. }
  71. } while ($nextToken);
  72. });
  73. }
  74. /**
  75. * Returns an iterator that iterates over the values of applying a JMESPath
  76. * search to each result yielded by the iterator as a flat sequence.
  77. *
  78. * @param string $expression JMESPath expression to apply to each result.
  79. *
  80. * @return \Iterator
  81. */
  82. public function search($expression)
  83. {
  84. // Apply JMESPath expression on each result, but as a flat sequence.
  85. return flatmap($this, function (Result $result) use ($expression) {
  86. return (array) $result->search($expression);
  87. });
  88. }
  89. /**
  90. * @return Result
  91. */
  92. public function current()
  93. {
  94. return $this->valid() ? $this->result : false;
  95. }
  96. public function key()
  97. {
  98. return $this->valid() ? $this->requestCount - 1 : null;
  99. }
  100. public function next()
  101. {
  102. $this->result = null;
  103. }
  104. public function valid()
  105. {
  106. if ($this->result) {
  107. return true;
  108. }
  109. if ($this->nextToken || !$this->requestCount) {
  110. $this->result = $this->client->execute(
  111. $this->createNextCommand($this->args, $this->nextToken)
  112. );
  113. $this->nextToken = $this->determineNextToken($this->result);
  114. $this->requestCount++;
  115. return true;
  116. }
  117. return false;
  118. }
  119. public function rewind()
  120. {
  121. $this->requestCount = 0;
  122. $this->nextToken = null;
  123. $this->result = null;
  124. }
  125. private function createNextCommand(array $args, array $nextToken = null)
  126. {
  127. return $this->client->getCommand($this->operation, array_merge($args, ($nextToken ?: [])));
  128. }
  129. private function determineNextToken(Result $result)
  130. {
  131. if (!$this->config['output_token']) {
  132. return null;
  133. }
  134. if ($this->config['more_results']
  135. && !$result->search($this->config['more_results'])
  136. ) {
  137. return null;
  138. }
  139. $nextToken = is_scalar($this->config['output_token'])
  140. ? [$this->config['input_token'] => $this->config['output_token']]
  141. : array_combine($this->config['input_token'], $this->config['output_token']);
  142. return array_filter(array_map(function ($outputToken) use ($result) {
  143. return $result->search($outputToken);
  144. }, $nextToken));
  145. }
  146. }