resource-iterators.rst 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. ==================
  2. Resource iterators
  3. ==================
  4. Web services often implement pagination in their responses which requires the end-user to issue a series of consecutive
  5. requests in order to fetch all of the data they asked for. Users of your web service client should not be responsible
  6. for implementing the logic involved in iterating through pages of results. Guzzle provides a simple resource iterator
  7. foundation to make it easier on web service client developers to offer a useful abstraction layer.
  8. Getting an iterator from a client
  9. ---------------------------------
  10. ResourceIteratorInterface Guzzle\Service\Client::getIterator($command [, array $commandOptions, array $iteratorOptions ])
  11. The ``getIterator`` method of a ``Guzzle\Service\ClientInterface`` object provides a convenient interface for
  12. instantiating a resource iterator for a specific command. This method implicitly uses a
  13. ``Guzzle\Service\Resource\ResourceIteratorFactoryInterface`` object to create resource iterators. Pass an
  14. instantiated command object or the name of a command in the first argument. When passing the name of a command, the
  15. command factory of the client will create the command by name using the ``$commandOptions`` array. The third argument
  16. may be used to pass an array of options to the constructor of the instantiated ``ResourceIteratorInterface`` object.
  17. .. code-block:: php
  18. $iterator = $client->getIterator('get_users');
  19. foreach ($iterator as $user) {
  20. echo $user['name'] . ' age ' . $user['age'] . PHP_EOL;
  21. }
  22. The above code sample might execute a single request or a thousand requests. As a consumer of a web service, I don't
  23. care. I just want to iterate over all of the users.
  24. Iterator options
  25. ~~~~~~~~~~~~~~~~
  26. The two universal options that iterators should support are ``limit`` and ``page_size``. Using the ``limit`` option
  27. tells the resource iterator to attempt to limit the total number of iterated resources to a specific amount. Keep in
  28. mind that this is not always possible due to limitations that may be inherent to a web service. The ``page_size``
  29. option is used to tell a resource iterator how many resources to request per page of results. Much like the ``limit``
  30. option, you can not rely on getting back exactly the number of resources your specify in the ``page_size`` option.
  31. .. note::
  32. The ``limit`` and ``page_size`` options can also be specified on an iterator using the ``setLimit($limit)`` and
  33. ``setPageSize($pageSize)`` methods.
  34. Resolving iterator class names
  35. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  36. The default resource iterator factory of a client object expects that your iterators are stored under the ``Model``
  37. folder of your client and that an iterator is names after the CamelCase name of a command followed by the word
  38. "Iterator". For example, if you wanted to create an iterator for the ``get_users`` command, then your iterator class
  39. would be ``Model\GetUsersIterator`` and would be stored in ``Model/GetUsersIterator.php``.
  40. Creating an iterator
  41. --------------------
  42. While not required, resource iterators in Guzzle typically iterate using a ``Guzzle\Service\Command\CommandInterface``
  43. object. ``Guzzle\Service\Resource\ResourceIterator``, the default iterator implementation that you should extend,
  44. accepts a command object and array of iterator options in its constructor. The command object passed to the resource
  45. iterator is expected to be ready to execute and not previously executed. The resource iterator keeps a reference of
  46. this command and clones the original command each time a subsequent request needs to be made to fetch more data.
  47. Implement the sendRequest method
  48. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  49. The most important thing (and usually the only thing) you need to do when creating a resource iterator is to implement
  50. the ``sendRequest()`` method of the resource iterator. The ``sendRequest()`` method is called when you begin
  51. iterating or if there are no resources left to iterate and it you expect to retrieve more resources by making a
  52. subsequent request. The ``$this->command`` property of the resource iterator is updated with a cloned copy of the
  53. original command object passed into the constructor of the iterator. Use this command object to issue your subsequent
  54. requests.
  55. The ``sendRequest()`` method must return an array of the resources you retrieved from making the subsequent call.
  56. Returning an empty array will stop the iteration. If you suspect that your web service client will occasionally return
  57. an empty result set but still requires further iteration, then you must implement a sort of loop in your
  58. ``sendRequest()`` method that will continue to issue subsequent requests until your reach the end of the paginated
  59. result set or until additional resources are retrieved from the web service.
  60. Update the nextToken property
  61. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  62. Beyond fetching more results, the ``sendRequest()`` method is responsible for updating the ``$this->nextToken``
  63. property of the iterator. Setting this property to anything other than null tells the iterator that issuing a
  64. subsequent request using the nextToken value will probably return more results. You must continually update this
  65. value in your ``sendRequest()`` method as each response is received from the web service.
  66. Example iterator
  67. ----------------
  68. Let's say you want to implement a resource iterator for the ``get_users`` command of your web service. The
  69. ``get_users`` command receives a response that contains a list of users, and if there are more pages of results to
  70. retrieve, returns a value called ``next_user``. This return value is known as the **next token** and should be used to
  71. issue subsequent requests.
  72. Assume the response to a ``get_users`` command returns JSON data that looks like this:
  73. .. code-block:: javascript
  74. {
  75. "users": [
  76. { "name": "Craig Johnson", "age": 10 },
  77. { "name": "Tom Barker", "age": 20 },
  78. { "name": "Bob Mitchell", "age": 74 }
  79. ],
  80. "next_user": "Michael Dowling"
  81. }
  82. Assume that because there is a ``next_user`` value, there will be more users if a subsequent request is issued. If the
  83. ``next_user`` value is missing or null, then we know there are no more results to fetch. Let's implement a resource
  84. iterator for this command.
  85. .. code-block:: php
  86. namespace MyService\Model;
  87. use Guzzle\Service\Resource\ResourceIterator;
  88. /**
  89. * Iterate over a get_users command
  90. */
  91. class GetUsersIterator extends ResourceIterator
  92. {
  93. protected function sendRequest()
  94. {
  95. // If a next token is set, then add it to the command
  96. if ($this->nextToken) {
  97. $this->command->set('next_user', $this->nextToken);
  98. }
  99. // Execute the command and parse the result
  100. $result = $this->command->execute();
  101. // Parse the next token
  102. $this->nextToken = isset($result['next_user']) ? $result['next_user'] : false;
  103. return $result['users'];
  104. }
  105. }
  106. As you can see, it's pretty simple to implement an iterator. There are a few things that you should notice from this
  107. example:
  108. 1. You do not need to create a new command in the ``sendRequest()`` method. A new command object is cloned from the
  109. original command passed into the constructor of the iterator before the ``sendRequest()`` method is called.
  110. Remember that the resource iterator expects a command that has not been executed.
  111. 2. When the ``sendRequest()`` method is first called, you will not have a ``$this->nextToken`` value, so always check
  112. before setting it on a command. Notice that the next token is being updated each time a request is sent.
  113. 3. After fetching more resources from the service, always return an array of resources.