1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
|
# Iterators
## Intro
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](http://php.net/manual/en/class.iterator.php), [ArrayAccess](http://www.php.net/manual/en/class.arrayaccess.php) and [Countable](http://php.net/manual/en/class.countable.php) 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
```php
$iterator = $computeService->flavorList();
```
There are two ways to traverse an iterator. The first is the longer, more traditional way:
```php
while ($iterator->valid()) {
$flavor = $iterator->current();
// do stuff..
echo $flavor->id;
$iterator->next();
}
```
There is also a shorter and more intuitive version:
```php
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:
```php
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
```php
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()`
|