# 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 (using `count` or `sizeof`), 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:

1. `Group::scalingGroup($data);`
2. `Group::getScalingGroup($data);`
3. `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()`