|
| 1 | +# Iterators |
| 2 | + |
| 3 | +## Intro |
| 4 | + |
| 5 | +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: |
| 6 | + |
| 7 | +- **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]`). |
| 8 | + |
| 9 | + |
| 10 | +- **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. |
| 11 | + |
| 12 | +## Common behaviour |
| 13 | + |
| 14 | +```php |
| 15 | +$iterator = $computeService->flavorList(); |
| 16 | +``` |
| 17 | + |
| 18 | +There are two ways to traverse an iterator. The first is the longer, more traditional way: |
| 19 | + |
| 20 | +```php |
| 21 | +while ($iterator->valid()) { |
| 22 | + $flavor = $iterator->current(); |
| 23 | + |
| 24 | + // do stuff.. |
| 25 | + echo $flavor->id; |
| 26 | + |
| 27 | + $iterator->next(); |
| 28 | +} |
| 29 | +``` |
| 30 | + |
| 31 | +There is also a shorter and more intuitive version: |
| 32 | + |
| 33 | +```php |
| 34 | +foreach ($iterator as $flavor) { |
| 35 | + // do stuff... |
| 36 | + echo $flavor->id; |
| 37 | +} |
| 38 | +``` |
| 39 | + |
| 40 | +Because the iterator implements PHP's native `Iterator` interface, it can inherit all the native functionality of traversible data structures with `foreach`. |
| 41 | + |
| 42 | +## Very important note |
| 43 | + |
| 44 | +Until now, users have been expected to do this: |
| 45 | + |
| 46 | +```php |
| 47 | +while ($flavor = $iterator->next()) { |
| 48 | + // ... |
| 49 | +} |
| 50 | +``` |
| 51 | + |
| 52 | +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. |
| 53 | + |
| 54 | +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. |
| 55 | + |
| 56 | +## Using paginated collections |
| 57 | + |
| 58 | +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. |
| 59 | + |
| 60 | +There are two key concepts: |
| 61 | + |
| 62 | +- **limit** is the amount of resources returned per page |
| 63 | +- **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 |
| 64 | + |
| 65 | +### Resource classes |
| 66 | + |
| 67 | +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`. |
| 68 | + |
| 69 | +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: |
| 70 | + |
| 71 | +1. `Group::scalingGroup($data);` |
| 72 | + |
| 73 | +2. `Group::getScalingGroup($data);` |
| 74 | + |
| 75 | +3. `Group::resource('ScalingGroup', $data);` |
| 76 | + |
| 77 | +where `$data` is the standard object. This list runs in order of precedence. |
| 78 | + |
| 79 | +## Setting up a PaginatedIterator |
| 80 | + |
| 81 | +```php |
| 82 | +use OpenCloud\Common\Collection\PaginatedIterator; |
| 83 | + |
| 84 | +$service = $client->computeService(); |
| 85 | + |
| 86 | +$flavors = PaginatedIterator::factory($service, array( |
| 87 | + 'resourceClass' => 'Flavor', |
| 88 | + 'baseUrl' => $service->getUrl('flavors') |
| 89 | + 'limit.total' => 350, |
| 90 | + 'limit.page' => 100, |
| 91 | + 'key.collection' => 'flavors' |
| 92 | +)); |
| 93 | + |
| 94 | +foreach ($flavors as $flavor) { |
| 95 | + echo $flavor->getId(); |
| 96 | +} |
| 97 | +``` |
| 98 | + |
| 99 | +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. |
| 100 | + |
| 101 | +### PaginatedIterator options |
| 102 | + |
| 103 | +There are certain configuration options that the paginated iterator needs to work. These are: |
| 104 | + |
| 105 | +Name|Description|Type|Required|Default |
| 106 | +---|---|---|---|--- |
| 107 | +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|- |
| 108 | +baseUrl|The base URL that is used for making new calls to the API for new pages|`Guzzle\Http\Url`|Yes|- |
| 109 | +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 |
| 110 | +limit.page|The amount of resources each page contains|int|No|100 |
| 111 | +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 |
| 112 | +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` |
| 113 | +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` |
| 114 | +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 |
| 115 | +request.method|The HTTP method used when making API calls for new pages|string|No|GET |
| 116 | +request.headers|The HTTP headers to send when making API calls for new pages|array|No|`array()` |
| 117 | +request.body|The HTTP entity body to send when making API calls for new pages|`Guzzle\Http\EntityBody`|No|`null` |
| 118 | +request.curlOptions|Additional cURL options to use when making API calls for new pages|array|No|`array()` |
0 commit comments