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)); } }