==================
|
Resource iterators
|
==================
|
|
Web services often implement pagination in their responses which requires the end-user to issue a series of consecutive
|
requests in order to fetch all of the data they asked for. Users of your web service client should not be responsible
|
for implementing the logic involved in iterating through pages of results. Guzzle provides a simple resource iterator
|
foundation to make it easier on web service client developers to offer a useful abstraction layer.
|
|
Getting an iterator from a client
|
---------------------------------
|
|
ResourceIteratorInterface Guzzle\Service\Client::getIterator($command [, array $commandOptions, array $iteratorOptions ])
|
|
The ``getIterator`` method of a ``Guzzle\Service\ClientInterface`` object provides a convenient interface for
|
instantiating a resource iterator for a specific command. This method implicitly uses a
|
``Guzzle\Service\Resource\ResourceIteratorFactoryInterface`` object to create resource iterators. Pass an
|
instantiated command object or the name of a command in the first argument. When passing the name of a command, the
|
command factory of the client will create the command by name using the ``$commandOptions`` array. The third argument
|
may be used to pass an array of options to the constructor of the instantiated ``ResourceIteratorInterface`` object.
|
|
.. code-block:: php
|
|
$iterator = $client->getIterator('get_users');
|
|
foreach ($iterator as $user) {
|
echo $user['name'] . ' age ' . $user['age'] . PHP_EOL;
|
}
|
|
The above code sample might execute a single request or a thousand requests. As a consumer of a web service, I don't
|
care. I just want to iterate over all of the users.
|
|
Iterator options
|
~~~~~~~~~~~~~~~~
|
|
The two universal options that iterators should support are ``limit`` and ``page_size``. Using the ``limit`` option
|
tells the resource iterator to attempt to limit the total number of iterated resources to a specific amount. Keep in
|
mind that this is not always possible due to limitations that may be inherent to a web service. The ``page_size``
|
option is used to tell a resource iterator how many resources to request per page of results. Much like the ``limit``
|
option, you can not rely on getting back exactly the number of resources your specify in the ``page_size`` option.
|
|
.. note::
|
|
The ``limit`` and ``page_size`` options can also be specified on an iterator using the ``setLimit($limit)`` and
|
``setPageSize($pageSize)`` methods.
|
|
Resolving iterator class names
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
The default resource iterator factory of a client object expects that your iterators are stored under the ``Model``
|
folder of your client and that an iterator is names after the CamelCase name of a command followed by the word
|
"Iterator". For example, if you wanted to create an iterator for the ``get_users`` command, then your iterator class
|
would be ``Model\GetUsersIterator`` and would be stored in ``Model/GetUsersIterator.php``.
|
|
Creating an iterator
|
--------------------
|
|
While not required, resource iterators in Guzzle typically iterate using a ``Guzzle\Service\Command\CommandInterface``
|
object. ``Guzzle\Service\Resource\ResourceIterator``, the default iterator implementation that you should extend,
|
accepts a command object and array of iterator options in its constructor. The command object passed to the resource
|
iterator is expected to be ready to execute and not previously executed. The resource iterator keeps a reference of
|
this command and clones the original command each time a subsequent request needs to be made to fetch more data.
|
|
Implement the sendRequest method
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
The most important thing (and usually the only thing) you need to do when creating a resource iterator is to implement
|
the ``sendRequest()`` method of the resource iterator. The ``sendRequest()`` method is called when you begin
|
iterating or if there are no resources left to iterate and it you expect to retrieve more resources by making a
|
subsequent request. The ``$this->command`` property of the resource iterator is updated with a cloned copy of the
|
original command object passed into the constructor of the iterator. Use this command object to issue your subsequent
|
requests.
|
|
The ``sendRequest()`` method must return an array of the resources you retrieved from making the subsequent call.
|
Returning an empty array will stop the iteration. If you suspect that your web service client will occasionally return
|
an empty result set but still requires further iteration, then you must implement a sort of loop in your
|
``sendRequest()`` method that will continue to issue subsequent requests until your reach the end of the paginated
|
result set or until additional resources are retrieved from the web service.
|
|
Update the nextToken property
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
Beyond fetching more results, the ``sendRequest()`` method is responsible for updating the ``$this->nextToken``
|
property of the iterator. Setting this property to anything other than null tells the iterator that issuing a
|
subsequent request using the nextToken value will probably return more results. You must continually update this
|
value in your ``sendRequest()`` method as each response is received from the web service.
|
|
Example iterator
|
----------------
|
|
Let's say you want to implement a resource iterator for the ``get_users`` command of your web service. The
|
``get_users`` command receives a response that contains a list of users, and if there are more pages of results to
|
retrieve, returns a value called ``next_user``. This return value is known as the **next token** and should be used to
|
issue subsequent requests.
|
|
Assume the response to a ``get_users`` command returns JSON data that looks like this:
|
|
.. code-block:: javascript
|
|
{
|
"users": [
|
{ "name": "Craig Johnson", "age": 10 },
|
{ "name": "Tom Barker", "age": 20 },
|
{ "name": "Bob Mitchell", "age": 74 }
|
],
|
"next_user": "Michael Dowling"
|
}
|
|
Assume that because there is a ``next_user`` value, there will be more users if a subsequent request is issued. If the
|
``next_user`` value is missing or null, then we know there are no more results to fetch. Let's implement a resource
|
iterator for this command.
|
|
.. code-block:: php
|
|
namespace MyService\Model;
|
|
use Guzzle\Service\Resource\ResourceIterator;
|
|
/**
|
* Iterate over a get_users command
|
*/
|
class GetUsersIterator extends ResourceIterator
|
{
|
protected function sendRequest()
|
{
|
// If a next token is set, then add it to the command
|
if ($this->nextToken) {
|
$this->command->set('next_user', $this->nextToken);
|
}
|
|
// Execute the command and parse the result
|
$result = $this->command->execute();
|
|
// Parse the next token
|
$this->nextToken = isset($result['next_user']) ? $result['next_user'] : false;
|
|
return $result['users'];
|
}
|
}
|
|
As you can see, it's pretty simple to implement an iterator. There are a few things that you should notice from this
|
example:
|
|
1. You do not need to create a new command in the ``sendRequest()`` method. A new command object is cloned from the
|
original command passed into the constructor of the iterator before the ``sendRequest()`` method is called.
|
Remember that the resource iterator expects a command that has not been executed.
|
2. When the ``sendRequest()`` method is first called, you will not have a ``$this->nextToken`` value, so always check
|
before setting it on a command. Notice that the next token is being updated each time a request is sent.
|
3. After fetching more resources from the service, always return an array of resources.
|