1313 * type : improvement
1414 * priority : normal
1515 * labels : iterator
16+ * @I Support retries on throwables when calling `list` on the client
17+ * type : feature
18+ * priority : normal
19+ * labels : error-handling, iterator
1620 */
1721class Iterator implements IteratorInterface
1822{
@@ -26,6 +30,39 @@ class Iterator implements IteratorInterface
2630 */
2731 protected $ cache ;
2832
33+ /**
34+ * The seconds/nanoseconds to sleep after requesting a page.
35+ *
36+ * Some API have rate limits that could be hit when iterating over a large
37+ * number of pages without a delay between page requests.
38+ *
39+ * For example:
40+ * - API has a limit of 10 requests per second.
41+ * - There's 50 pages for the query.
42+ * - API responds to each request fast e.g. in milliseconds.
43+ * - There's no delay between requesting the next page i.e. processing the
44+ * results is also fast.
45+ *
46+ * In such cases, looping over the iterator will result in hitting the API
47+ * rate limits and some of the pages failing to be fetched.
48+ *
49+ * Providing a delay will instruct the iterator to sleep after fetching a
50+ * page for that amount of time. For example, in the case above the delay
51+ * could be set to 0 seconds and 100000000 nanoseconds (i.e. 0.1 seconds)
52+ * which will ensure that the 10 requests per second will not be exceeded
53+ * regardless of response and processing times.
54+ *
55+ * The delay must be given as an array containing the number of seconds in
56+ * its first element and the number of nanoseconds in its second element.
57+ *
58+ * In the example above that would be [0, 100000000];
59+ *
60+ * @var array
61+ *
62+ * @see time_nanosleep()
63+ */
64+ protected $ delay ;
65+
2966 /**
3067 * The client that will be used to make requests to the API.
3168 *
@@ -82,13 +119,17 @@ class Iterator implements IteratorInterface
82119 * the requests.
83120 * @param bool $cache
84121 * Whether to reuse cached results or not.
122+ * @param array $delay
123+ * A pair of seconds/nanoseconds that will determine the delay after
124+ * fetching a page.
85125 */
86126 public function __construct (
87127 ClientInterface $ client ,
88128 int $ pageIndex = null ,
89129 int $ limit = null ,
90130 array $ query = [],
91- bool $ cache = true
131+ bool $ cache = true ,
132+ array $ delay = null
92133 ) {
93134 $ this ->client = $ client ;
94135
@@ -101,6 +142,11 @@ public function __construct(
101142
102143 $ this ->query = $ query ;
103144 $ this ->cache = $ cache ;
145+
146+ if ($ delay ) {
147+ $ this ->delay = $ delay ;
148+ $ this ->validateDelay ();
149+ }
104150 }
105151
106152 /**
@@ -131,6 +177,11 @@ public function current(): \CachingIterator
131177 }
132178
133179 $ this ->pages [$ this ->position ]->rewind ();
180+
181+ if ($ this ->delay !== null ) {
182+ time_nanosleep ($ this ->delay [0 ], $ this ->delay [1 ]);
183+ }
184+
134185 return $ this ->pages [$ this ->position ];
135186 }
136187
@@ -274,4 +325,29 @@ public function getAllItems()
274325 $ this ->rewind ();
275326 return $ items ;
276327 }
328+
329+ /**
330+ * Validates that the iterator delay is in the expected format.
331+ *
332+ * If set, it must be an array containing the seconds and nanoseconds as
333+ * integer numbers, as expected by time_nanosleep().
334+ *
335+ * @throws \InvalidArgumentException
336+ * When the delay is set but in an incorrect format.
337+ */
338+ protected function validateDelay () {
339+ if (!isset ($ this ->delay )) {
340+ return ;
341+ }
342+ if (!isset ($ this ->delay [0 ]) || !is_int ($ this ->delay [0 ])) {
343+ throw new \InvalidArgumentException (
344+ 'You must provide the seconds of the delay as an integer. '
345+ );
346+ }
347+ if (!isset ($ this ->delay [1 ]) || !is_int ($ this ->delay [1 ])) {
348+ throw new \InvalidArgumentException (
349+ 'You must provide the nanoseconds of the delay as an integer. '
350+ );
351+ }
352+ }
277353}
0 commit comments