3
3
namespace SilverStripe \Workable ;
4
4
5
5
use GuzzleHttp \ClientInterface ;
6
- use GuzzleHttp \Exception \ClientException ;
7
- use Monolog \Logger ;
8
- use RuntimeException ;
6
+ use GuzzleHttp \Exception \RequestException ;
9
7
use Psr \Log \LoggerInterface ;
10
8
use SilverStripe \ORM \ArrayList ;
11
9
use SilverStripe \Core \Flushable ;
12
- use SilverStripe \View \ArrayData ;
13
10
use SilverStripe \Core \Extensible ;
14
11
use Psr \SimpleCache \CacheInterface ;
15
12
use SilverStripe \Core \Injector \Injector ;
@@ -65,7 +62,7 @@ public function getJobs(array $params = []): ArrayList
65
62
}
66
63
67
64
$ list = ArrayList::create ();
68
- $ response = $ this ->callHttpClient ('jobs ' , $ params );
65
+ $ response = $ this ->callWorkableApi ('jobs ' , $ params );
69
66
70
67
if (!$ response ) {
71
68
return $ list ;
@@ -97,7 +94,7 @@ public function getJob(string $shortcode, array $params = []): ?WorkableResult
97
94
}
98
95
99
96
$ job = null ;
100
- $ response = $ this ->callHttpClient ('jobs/ ' . $ shortcode , $ params );
97
+ $ response = $ this ->callWorkableApi ('jobs/ ' . $ shortcode , $ params );
101
98
102
99
if ($ response && isset ($ response ['id ' ])) {
103
100
$ job = WorkableResult::create ($ response );
@@ -109,8 +106,7 @@ public function getJob(string $shortcode, array $params = []): ?WorkableResult
109
106
110
107
/**
111
108
* Gets all the jobs from the workable API, populating each job with its full data
112
- * Note: This calls the API multiple times so should be used with caution, see
113
- * rate limiting docs https://workable.readme.io/docs/rate-limits
109
+ * Note: This calls the API multiple times so should be used with caution
114
110
* @param array $params Array of params, e.g. ['state' => 'published'].
115
111
* see https://workable.readme.io/docs/jobs for full list of query params
116
112
* @return ArrayList
@@ -124,7 +120,7 @@ public function getFullJobs($params = [])
124
120
}
125
121
126
122
$ list = ArrayList::create ();
127
- $ response = $ this ->callHttpClient ('jobs ' , $ params );
123
+ $ response = $ this ->callWorkableApi ('jobs ' , $ params );
128
124
129
125
if (!$ response ) {
130
126
return $ list ;
@@ -142,30 +138,67 @@ public function getFullJobs($params = [])
142
138
}
143
139
144
140
/**
145
- * Wrapper method to configure the RestfulService, make the call, and handle errors
141
+ * Sends request to Workable API.
142
+ * Should it exceed the rate limit, this is caught and put to sleep until the next interval.
143
+ * The interval duration is provided by Workable via a header.
144
+ * When its awaken, it will call itself again, this repeats until its complete.
145
+ * This returns a json body from the response.
146
+ *
147
+ * Note: See rate limit docs from Workable https://workable.readme.io/docs/rate-limits
146
148
* @param string $url
147
149
* @param array $params
148
150
* @param string $method
149
151
*
150
- * @throws RuntimeException if client is not configured correctly
151
- * @throws ClientException if request fails
152
- *
152
+ * @throws RequestException if client is not configured correctly, handles 429 error
153
+
153
154
* @return array JSON as array
154
155
*/
155
- public function callHttpClient (string $ url , array $ params = [], string $ method = 'GET ' ): array
156
+ public function callWorkableApi (string $ url , array $ params = [], string $ method = 'GET ' ): array
156
157
{
157
158
try {
158
159
$ response = $ this ->httpClient ->request ($ method , $ url , ['query ' => $ params ]);
159
- } catch (\RuntimeException $ e ) {
160
- Injector::inst ()->get (LoggerInterface::class)->warning (
161
- 'Failed to retrieve valid response from workable ' ,
162
- ['exception ' => $ e ]
163
- );
164
-
165
- throw $ e ;
160
+ return json_decode ($ response ->getBody (), true );
161
+ }
162
+ catch (RequestException $ e ){
163
+ if ($ e ->hasResponse ()){
164
+ $ errorResponse = $ e ->getResponse ();
165
+ $ statusCode = $ errorResponse ->getStatusCode ();
166
+
167
+ if ($ statusCode === 429 ) {
168
+ Injector::inst ()->get (LoggerInterface::class)->info (
169
+ 'Rate limit exceeded - sleeping until next interval '
170
+ );
171
+
172
+ $ this ->sleepUntil ($ errorResponse ->getHeader ('X-Rate-Limit-Reset ' ));
173
+
174
+ return $ this ->callWorkableApi ($ url , $ params , $ method );
175
+ }
176
+ else {
177
+ Injector::inst ()->get (LoggerInterface::class)->warning (
178
+ 'Failed to retrieve valid response from workable ' ,
179
+ ['exception ' => $ e ]
180
+ );
181
+
182
+ throw $ e ;
183
+ }
184
+ }
166
185
}
186
+ }
167
187
168
- return json_decode ($ response ->getBody (), true );
188
+ /**
189
+ * Sleeps until the next interval.
190
+ * Should the interval header be empty, the script sleeps for 10 seconds - Workable's default interval.
191
+ * @param array $resetIntervalHeader
192
+ */
193
+ private function sleepUntil ($ resetIntervalHeader ){
194
+ $ defaultSleepInterval = 10 ;
195
+
196
+ if (!empty ($ resetIntervalHeader )){
197
+ time_sleep_until ($ resetIntervalHeader [0 ]);
198
+ }
199
+ else {
200
+ sleep ($ defaultSleepInterval );
201
+ }
169
202
}
170
203
171
204
/**
@@ -177,6 +210,7 @@ public static function flush()
177
210
}
178
211
179
212
/**
213
+ * Gets any cached data. If there is no cached data, a blank cache is created.
180
214
* @return CacheInterface
181
215
*/
182
216
public function getCache (): CacheInterface
@@ -189,6 +223,7 @@ public function getCache(): CacheInterface
189
223
}
190
224
191
225
/**
226
+ * Sets the cache.
192
227
* @param CacheInterface $cache
193
228
* @return self
194
229
*/
0 commit comments