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