Iterators¶
Iterators allow you to traverse over collections of your resources in an efficient and easy way. Currently there are two Iterators provided by the SDK:
- ResourceIterator. The standard iterator class that implements
SPL’s standard
Iterator,
ArrayAccess
and Countable
interfaces. In short, this allows you to traverse this object (using
foreach
), count its internal elements like an array (usingcount
orsizeof
), and access its internal elements like an array (using$iterator[1]
). - PaginatedIterator. This is a child of ResourceIterator, and as such inherits all of its functionality. The difference however is that when it reaches the end of the current collection, it attempts to construct a URL to access the API based on predictive paginated collection templates.
Common behaviour¶
$iterator = $computeService->flavorList();
There are two ways to traverse an iterator. The first is the longer, more traditional way:
while ($iterator->valid()) {
$flavor = $iterator->current();
// do stuff..
echo $flavor->id;
$iterator->next();
}
There is also a shorter and more intuitive version:
foreach ($iterator as $flavor) {
// do stuff...
echo $flavor->id;
}
Because the iterator implements PHP’s native Iterator
interface, it
can inherit all the native functionality of traversible data structures
with foreach
.
Very important note¶
Until now, users have been expected to do this:
while ($flavor = $iterator->next()) {
// ...
}
which is incorrect. The single responsibility of next
is to move
the internal pointer forward. It is the job of current
to retrieve
the current element.
For your convenience, these two Iterator classes are fully backward compatible: they exhibit all the functionality you’d expect from a correctly implemented iterator, but they also allow previous behaviour.
Using paginated collections¶
For large collections, such as retrieving DataObjects from CloudFiles/Swift, you need to use pagination. Each resource will have a different limit per page; so once that page is traversed, there needs to be another API call to retrieve to next page’s resources.
There are two key concepts:
- limit is the amount of resources returned per page
- marker is the way you define a starting point. It is some form of identifier that allows the collection to begin from a specific resource
Resource classes¶
When the iterator returns a current element in the internal list, it
populates the relevant resource class with all the data returned to the
API. In most cases, a stdClass
object will become an instance of
OpenCloud\Common\PersistentObject
.
In order for this instantiation to happen, the resourceClass
option
must correspond to some method in the parent class that creates the
resource. For example, if we specify ‘ScalingPolicy’ as the
resourceClass
, the parent object (in this case
OpenCloud\Autoscale\Group
, needs to have some method will allows the
iterator to instantiate the child resource class. These are all valid:
Group::scalingGroup($data);
Group::getScalingGroup($data);
Group::resource('ScalingGroup', $data);
where $data
is the standard object. This list runs in order of
precedence.
Setting up a PaginatedIterator¶
use OpenCloud\Common\Collection\PaginatedIterator;
$service = $client->computeService();
$flavors = PaginatedIterator::factory($service, array(
'resourceClass' => 'Flavor',
'baseUrl' => $service->getUrl('flavors')
'limit.total' => 350,
'limit.page' => 100,
'key.collection' => 'flavors'
));
foreach ($flavors as $flavor) {
echo $flavor->getId();
}
As you can see, there are a lot of configuration parameters to pass in -
and getting it right can be quite fiddly, involving a lot of API
research. For this reason, using the convenience methods like
flavorList
is recommended because it hides the complexity.
PaginatedIterator options¶
There are certain configuration options that the paginated iterator needs to work. These are:
Name | Description | Type | Required | Default |
---|---|---|---|---|
resourceClass | The resource class that is instantiated when the current element is retrieved. This is relative to the parent/service which called the iterator. | string | Yes | |
baseUrl | The base URL that is used for making new calls to the API for new pages | Guzzle\Http\Url |
Yes | |
limit.total | The total amount of resources you want to traverse in your collection. The iterator will stop as this limit is reached, regardless if there are more items in the list | int | No | 10000 |
limit.page | The amount of resources each page contains | int | No | 100 |
key.links | Often, API responses will contain “links” that allow easy access to the next page of a resource collection. This option specifies what that JSON element is called (its key). For example, for Rackspace Compute images it is images_links . |
string | No | links |
key.collection | The top-level key for the array of resources. For example, servers are returned with this data structure: {"servers": [...]} . The key.collection value in this case would be servers . |
string | No | null |
key.collectionElement | Rarely used. But it indicates the key name for each nested resource element. KeyPairs, for example, are listed like this: {"keypairs": [ {"keypair": {...}} ] } . So in this case the collectionElement key would be keypair . |
string | No | null |
key.marker | The value used as the marker. It needs to represent a valid property in the JSON resource objects. Often it is id or name . |
string | No | name |
request.method | The HTTP method used when making API calls for new pages | string | No | GET |
request.headers | The HTTP headers to send when making API calls for new pages | array | No | array() |
request.body | The HTTP entity body to send when making API calls for new pages | Guzzle\Http\EntityBody |
No | null |
request.curlOptions | Additional cURL options to use when making API calls for new pages | array | No | array() |