Skip to content

Commit 0cfdd2e

Browse files
committed
Added support for delay between page requests
1 parent bcd6042 commit 0cfdd2e

File tree

2 files changed

+79
-1
lines changed

2 files changed

+79
-1
lines changed

src/ClientInterface.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ interface ClientInterface
2020
* - page (int): The index of the page to get, if the resource supports
2121
* paging.
2222
* - limit (int): The number of items to get.
23+
* - delay (int): The number of microseconds to wait after fetching a
24+
* page. It can be used to avoid hitting API rate limits.
2325
* - bypass_iterator (bool): The items are normally returned wrapped in an
2426
* API iterator. When the `bypass_iterator` option is set to, the items
2527
* should be returned without that extra wrapper iterator i.e. in just

src/Iterator.php

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@
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
*/
1721
class 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

Comments
 (0)