<?php
namespace Guzzle\Service\Resource;
use Guzzle\Common\AbstractHasDispatcher;
use Guzzle\Service\Command\CommandInterface;
abstract class ResourceIterator extends AbstractHasDispatcher implements ResourceIteratorInterface
{
/** @var CommandInterface Command used to send requests */
protected $command;
/** @var CommandInterface First sent command */
protected $originalCommand;
/** @var array Currently loaded resources */
protected $resources;
/** @var int Total number of resources that have been retrieved */
protected $retrievedCount = 0;
/** @var int Total number of resources that have been iterated */
protected $iteratedCount = 0;
/** @var string NextToken/Marker for a subsequent request */
protected $nextToken = false;
/** @var int Maximum number of resources to fetch per request */
protected $pageSize;
/** @var int Maximum number of resources to retrieve in total */
protected $limit;
/** @var int Number of requests sent */
protected $requestCount = 0;
/** @var array Initial data passed to the constructor */
protected $data = array();
/** @var bool Whether or not the current value is known to be invalid */
protected $invalid;
public static function getAllEvents()
{
return array(
// About to issue another command to get more results
'resource_iterator.before_send',
// Issued another command to get more results
'resource_iterator.after_send'
);
}
/**
* @param CommandInterface $command Initial command used for iteration
* @param array $data Associative array of additional parameters. You may specify any number of custom
* options for an iterator. Among these options, you may also specify the following values:
* - limit: Attempt to limit the maximum number of resources to this amount
* - page_size: Attempt to retrieve this number of resources per request
*/
public function __construct(CommandInterface $command, array $data = array())
{
// Clone the command to keep track of the originating command for rewind
$this->originalCommand = $command;
// Parse options from the array of options
$this->data = $data;
$this->limit = array_key_exists('limit', $data) ? $data['limit'] : 0;
$this->pageSize = array_key_exists('page_size', $data) ? $data['page_size'] : false;
}
/**
* Get all of the resources as an array (Warning: this could issue a large number of requests)
*
* @return array
*/
public function toArray()
{
return iterator_to_array($this, false);
}
public function setLimit($limit)
{
$this->limit = $limit;
$this->resetState();
return $this;
}
public function setPageSize($pageSize)
{
$this->pageSize = $pageSize;
$this->resetState();
return $this;
}
/**
* Get an option from the iterator
*
* @param string $key Key of the option to retrieve
*
* @return mixed|null Returns NULL if not set or the value if set
*/
public function get($key)
{
return array_key_exists($key, $this->data) ? $this->data[$key] : null;
}
/**
* Set an option on the iterator
*
* @param string $key Key of the option to set
* @param mixed $value Value to set for the option
*
* @return ResourceIterator
*/
public function set($key, $value)
{
$this->data[$key] = $value;
return $this;
}
public function current()
{
return $this->resources ? current($this->resources) : false;
}
public function key()
{
return max(0, $this->iteratedCount - 1);
}
public function count()
{
return $this->retrievedCount;
}
/**
* Get the total number of requests sent
*
* @return int
*/
public function getRequestCount()
{
return $this->requestCount;
}
/**
* Rewind the Iterator to the first element and send the original command
*/
public function rewind()
{
// Use the original command
$this->command = clone $this->originalCommand;
$this->resetState();
$this->next();
}
public function valid()
{
return !$this->invalid && (!$this->resources || $this->current() || $this->nextToken)
&& (!$this->limit || $this->iteratedCount < $this->limit + 1);
}
public function next()
{
$this->iteratedCount++;
// Check if a new set of resources needs to be retrieved
$sendRequest = false;
if (!$this->resources) {
$sendRequest = true;
} else {
// iterate over the internal array
$current = next($this->resources);
$sendRequest = $current === false && $this->nextToken && (!$this->limit || $this->iteratedCount < $this->limit + 1);
}
if ($sendRequest) {
$this->dispatch('resource_iterator.before_send', array(
'iterator' => $this,
'resources' => $this->resources
));
// Get a new command object from the original command
$this->command = clone $this->originalCommand;
// Send a request and retrieve the newly loaded resources
$this->resources = $this->sendRequest();
$this->requestCount++;
// If no resources were found, then the last request was not needed
// and iteration must stop
if (empty($this->resources)) {
$this->invalid = true;
} else {
// Add to the number of retrieved resources
$this->retrievedCount += count($this->resources);
// Ensure that we rewind to the beginning of the array
reset($this->resources);
}
$this->dispatch('resource_iterator.after_send', array(
'iterator' => $this,
'resources' => $this->resources
));
}
}
/**
* Retrieve the NextToken that can be used in other iterators.
*
* @return string Returns a NextToken
*/
public function getNextToken()
{
return $this->nextToken;
}
/**
* Returns the value that should be specified for the page size for a request that will maintain any hard limits,
* but still honor the specified pageSize if the number of items retrieved + pageSize < hard limit
*
* @return int Returns the page size of the next request.
*/
protected function calculatePageSize()
{
if ($this->limit && $this->iteratedCount + $this->pageSize > $this->limit) {
return 1 + ($this->limit - $this->iteratedCount);
}
return (int) $this->pageSize;
}
/**
* Reset the internal state of the iterator without triggering a rewind()
*/
protected function resetState()
{
$this->iteratedCount = 0;
$this->retrievedCount = 0;
$this->nextToken = false;
$this->resources = null;
$this->invalid = false;
}
/**
* Send a request to retrieve the next page of results. Hook for subclasses to implement.
*
* @return array Returns the newly loaded resources
*/
abstract protected function sendRequest();
}