1 | 1 |
deleted file mode 100644 |
... | ... |
@@ -1,464 +0,0 @@ |
1 |
-<?php |
|
2 |
- |
|
3 |
-namespace Telnyx; |
|
4 |
- |
|
5 |
-/** |
|
6 |
- * Class ApiRequestor |
|
7 |
- * |
|
8 |
- * @package Telnyx |
|
9 |
- */ |
|
10 |
-class ApiRequestor |
|
11 |
-{ |
|
12 |
- /** |
|
13 |
- * @var string|null |
|
14 |
- */ |
|
15 |
- private $_apiKey; |
|
16 |
- |
|
17 |
- /** |
|
18 |
- * @var string |
|
19 |
- */ |
|
20 |
- private $_apiBase; |
|
21 |
- |
|
22 |
- /** |
|
23 |
- * @var HttpClient\ClientInterface |
|
24 |
- */ |
|
25 |
- private static $_httpClient; |
|
26 |
- |
|
27 |
- /** |
|
28 |
- * @var RequestTelemetry |
|
29 |
- */ |
|
30 |
- private static $requestTelemetry; |
|
31 |
- |
|
32 |
- /** |
|
33 |
- * ApiRequestor constructor. |
|
34 |
- * |
|
35 |
- * @param string|null $apiKey |
|
36 |
- * @param string|null $apiBase |
|
37 |
- */ |
|
38 |
- public function __construct($apiKey = null, $apiBase = null) |
|
39 |
- { |
|
40 |
- $this->_apiKey = $apiKey; |
|
41 |
- if (!$apiBase) { |
|
42 |
- $apiBase = Telnyx::$apiBase; |
|
43 |
- } |
|
44 |
- $this->_apiBase = $apiBase; |
|
45 |
- } |
|
46 |
- |
|
47 |
- /** |
|
48 |
- * Creates a telemetry json blob for use in 'X-Telnyx-Client-Telemetry' headers |
|
49 |
- * @static |
|
50 |
- * |
|
51 |
- * @param RequestTelemetry $requestTelemetry |
|
52 |
- * @return string |
|
53 |
- */ |
|
54 |
- private static function _telemetryJson($requestTelemetry) |
|
55 |
- { |
|
56 |
- $payload = array( |
|
57 |
- 'last_request_metrics' => array( |
|
58 |
- 'request_id' => $requestTelemetry->requestId, |
|
59 |
- 'request_duration_ms' => $requestTelemetry->requestDuration, |
|
60 |
- )); |
|
61 |
- |
|
62 |
- $result = json_encode($payload); |
|
63 |
- if ($result != false) { |
|
64 |
- return $result; |
|
65 |
- } else { |
|
66 |
- Telnyx::getLogger()->error("Serializing telemetry payload failed!"); |
|
67 |
- return "{}"; |
|
68 |
- } |
|
69 |
- } |
|
70 |
- |
|
71 |
- /** |
|
72 |
- * @static |
|
73 |
- * |
|
74 |
- * @param ApiResource|bool|array|mixed $d |
|
75 |
- * |
|
76 |
- * @return ApiResource|array|string|mixed |
|
77 |
- */ |
|
78 |
- private static function _encodeObjects($d) |
|
79 |
- { |
|
80 |
- if ($d instanceof ApiResource) { |
|
81 |
- return Util\Util::utf8($d->id); |
|
82 |
- } elseif ($d === true) { |
|
83 |
- return 'true'; |
|
84 |
- } elseif ($d === false) { |
|
85 |
- return 'false'; |
|
86 |
- } elseif (is_array($d)) { |
|
87 |
- $res = []; |
|
88 |
- foreach ($d as $k => $v) { |
|
89 |
- $res[$k] = self::_encodeObjects($v); |
|
90 |
- } |
|
91 |
- return $res; |
|
92 |
- } else { |
|
93 |
- return Util\Util::utf8($d); |
|
94 |
- } |
|
95 |
- } |
|
96 |
- |
|
97 |
- /** |
|
98 |
- * @param string $method |
|
99 |
- * @param string $url |
|
100 |
- * @param array|null $params |
|
101 |
- * @param array|null $headers |
|
102 |
- * |
|
103 |
- * @return array An array whose first element is an API response and second |
|
104 |
- * element is the API key used to make the request. |
|
105 |
- * @throws Error\Api |
|
106 |
- * @throws Error\Authentication |
|
107 |
- * @throws Error\Card |
|
108 |
- * @throws Error\InvalidRequest |
|
109 |
- * @throws Error\Permission |
|
110 |
- * @throws Error\RateLimit |
|
111 |
- * @throws Error\Idempotency |
|
112 |
- * @throws Error\ApiConnection |
|
113 |
- */ |
|
114 |
- public function request($method, $url, $params = null, $headers = null) |
|
115 |
- { |
|
116 |
- $params = $params ?: []; |
|
117 |
- $headers = $headers ?: []; |
|
118 |
- list($rbody, $rcode, $rheaders, $myApiKey) = |
|
119 |
- $this->_requestRaw($method, $url, $params, $headers); |
|
120 |
- $json = $this->_interpretResponse($rbody, $rcode, $rheaders); |
|
121 |
- $resp = new ApiResponse($rbody, $rcode, $rheaders, $json); |
|
122 |
- return [$resp, $myApiKey]; |
|
123 |
- } |
|
124 |
- |
|
125 |
- /** |
|
126 |
- * @param string $rbody A JSON string. |
|
127 |
- * @param int $rcode |
|
128 |
- * @param array $rheaders |
|
129 |
- * @param array $resp |
|
130 |
- * |
|
131 |
- * @throws Error\InvalidRequest if the error is caused by the user. |
|
132 |
- * @throws Error\Authentication if the error is caused by a lack of |
|
133 |
- * permissions. |
|
134 |
- * @throws Error\Permission if the error is caused by insufficient |
|
135 |
- * permissions. |
|
136 |
- * @throws Error\Card if the error is the error code is 402 (payment |
|
137 |
- * required) |
|
138 |
- * @throws Error\InvalidRequest if the error is caused by the user. |
|
139 |
- * @throws Error\Idempotency if the error is caused by an idempotency key. |
|
140 |
- * @throws Error\Permission if the error is caused by insufficient |
|
141 |
- * permissions. |
|
142 |
- * @throws Error\RateLimit if the error is caused by too many requests |
|
143 |
- * hitting the API. |
|
144 |
- * @throws Error\Api otherwise. |
|
145 |
- */ |
|
146 |
- public function handleErrorResponse($rbody, $rcode, $rheaders, $resp) |
|
147 |
- { |
|
148 |
- if (!is_array($resp) || !isset($resp['error'])) { |
|
149 |
- $msg = "Invalid response object from API: $rbody " |
|
150 |
- . "(HTTP response code was $rcode)"; |
|
151 |
- throw new Error\Api($msg, $rcode, $rbody, $resp, $rheaders); |
|
152 |
- } |
|
153 |
- |
|
154 |
- $errorData = $resp['error']; |
|
155 |
- |
|
156 |
- #echo $rbody;exit; |
|
157 |
- |
|
158 |
- $error = null; |
|
159 |
- if (!$error) { |
|
160 |
- $error = self::_specificAPIError($rbody, $rcode, $rheaders, $resp, $errorData); |
|
161 |
- } |
|
162 |
- |
|
163 |
- throw $error; |
|
164 |
- } |
|
165 |
- |
|
166 |
- /** |
|
167 |
- * @static |
|
168 |
- * |
|
169 |
- * @param string $rbody |
|
170 |
- * @param int $rcode |
|
171 |
- * @param array $rheaders |
|
172 |
- * @param array $resp |
|
173 |
- * @param array $errorData |
|
174 |
- * |
|
175 |
- * @return Error\RateLimit|Error\Idempotency|Error\InvalidRequest|Error\Authentication|Error\Card|Error\Permission|Error\Api |
|
176 |
- */ |
|
177 |
- private static function _specificAPIError($rbody, $rcode, $rheaders, $resp, $errorData) |
|
178 |
- { |
|
179 |
- $msg = isset($errorData['message']) ? $errorData['message'] : null; |
|
180 |
- $param = isset($errorData['param']) ? $errorData['param'] : null; |
|
181 |
- $code = isset($errorData['code']) ? $errorData['code'] : null; |
|
182 |
- $type = isset($errorData['type']) ? $errorData['type'] : null; |
|
183 |
- |
|
184 |
- switch ($rcode) { |
|
185 |
- case 400: |
|
186 |
- // 'rate_limit' code is deprecated, but left here for backwards compatibility |
|
187 |
- // for API versions earlier than 2015-09-08 |
|
188 |
- if ($code == 'rate_limit') { |
|
189 |
- return new Error\RateLimit($msg, $param, $rcode, $rbody, $resp, $rheaders); |
|
190 |
- } |
|
191 |
- if ($type == 'idempotency_error') { |
|
192 |
- return new Error\Idempotency($msg, $rcode, $rbody, $resp, $rheaders); |
|
193 |
- } |
|
194 |
- |
|
195 |
- // intentional fall-through |
|
196 |
- // no break |
|
197 |
- case 404: |
|
198 |
- return new Error\InvalidRequest($msg, $param, $rcode, $rbody, $resp, $rheaders); |
|
199 |
- case 401: |
|
200 |
- return new Error\Authentication($msg, $rcode, $rbody, $resp, $rheaders); |
|
201 |
- case 402: |
|
202 |
- return new Error\Card($msg, $param, $code, $rcode, $rbody, $resp, $rheaders); |
|
203 |
- case 403: |
|
204 |
- return new Error\Permission($msg, $rcode, $rbody, $resp, $rheaders); |
|
205 |
- case 429: |
|
206 |
- return new Error\RateLimit($msg, $param, $rcode, $rbody, $resp, $rheaders); |
|
207 |
- default: |
|
208 |
- return new Error\Api($msg, $rcode, $rbody, $resp, $rheaders); |
|
209 |
- } |
|
210 |
- } |
|
211 |
- |
|
212 |
- /** |
|
213 |
- * @static |
|
214 |
- * |
|
215 |
- * @param null|array $appInfo |
|
216 |
- * |
|
217 |
- * @return null|string |
|
218 |
- */ |
|
219 |
- private static function _formatAppInfo($appInfo) |
|
220 |
- { |
|
221 |
- if ($appInfo !== null) { |
|
222 |
- $string = $appInfo['name']; |
|
223 |
- if ($appInfo['version'] !== null) { |
|
224 |
- $string .= '/' . $appInfo['version']; |
|
225 |
- } |
|
226 |
- if ($appInfo['url'] !== null) { |
|
227 |
- $string .= ' (' . $appInfo['url'] . ')'; |
|
228 |
- } |
|
229 |
- return $string; |
|
230 |
- } else { |
|
231 |
- return null; |
|
232 |
- } |
|
233 |
- } |
|
234 |
- |
|
235 |
- /** |
|
236 |
- * @static |
|
237 |
- * |
|
238 |
- * @param string $apiKey |
|
239 |
- * @param null $clientInfo |
|
240 |
- * |
|
241 |
- * @return array |
|
242 |
- */ |
|
243 |
- private static function _defaultHeaders($apiKey, $clientInfo = null) |
|
244 |
- { |
|
245 |
- $uaString = 'Telnyx/v2 PhpBindings/' . Telnyx::VERSION; |
|
246 |
- |
|
247 |
- $langVersion = phpversion(); |
|
248 |
- $uname = php_uname(); |
|
249 |
- |
|
250 |
- $appInfo = Telnyx::getAppInfo(); |
|
251 |
- $ua = [ |
|
252 |
- 'bindings_version' => Telnyx::VERSION, |
|
253 |
- 'lang' => 'php', |
|
254 |
- 'lang_version' => $langVersion, |
|
255 |
- 'publisher' => 'telnyx', |
|
256 |
- 'uname' => $uname, |
|
257 |
- ]; |
|
258 |
- if ($clientInfo) { |
|
259 |
- $ua = array_merge($clientInfo, $ua); |
|
260 |
- } |
|
261 |
- if ($appInfo !== null) { |
|
262 |
- $uaString .= ' ' . self::_formatAppInfo($appInfo); |
|
263 |
- $ua['application'] = $appInfo; |
|
264 |
- } |
|
265 |
- |
|
266 |
- $defaultHeaders = [ |
|
267 |
- 'X-Telnyx-Client-User-Agent' => json_encode($ua), |
|
268 |
- 'User-Agent' => $uaString, |
|
269 |
- 'Authorization' => 'Bearer ' . $apiKey, |
|
270 |
- ]; |
|
271 |
- return $defaultHeaders; |
|
272 |
- } |
|
273 |
- |
|
274 |
- /** |
|
275 |
- * @param string $method |
|
276 |
- * @param string $url |
|
277 |
- * @param array $params |
|
278 |
- * @param array $headers |
|
279 |
- * |
|
280 |
- * @return array |
|
281 |
- * @throws Error\Api |
|
282 |
- * @throws Error\ApiConnection |
|
283 |
- * @throws Error\Authentication |
|
284 |
- */ |
|
285 |
- private function _requestRaw($method, $url, $params, $headers) |
|
286 |
- { |
|
287 |
- $myApiKey = $this->_apiKey; |
|
288 |
- if (!$myApiKey) { |
|
289 |
- $myApiKey = Telnyx::$apiKey; |
|
290 |
- } |
|
291 |
- |
|
292 |
- if (!$myApiKey) { |
|
293 |
- $msg = 'No API key provided. (HINT: set your API key using ' |
|
294 |
- . '"Telnyx::setApiKey(<API-KEY>)". You can generate API keys from ' |
|
295 |
- . 'the Telnyx web interface. See https://developers.telnyx.com/docs/v2/development/authentication ' |
|
296 |
- . 'for details, or email support@telnyx.com if you have any questions.'; |
|
297 |
- throw new Error\Authentication($msg); |
|
298 |
- } |
|
299 |
- |
|
300 |
- // Clients can supply arbitrary additional keys to be included in the |
|
301 |
- // X-Telnyx-Client-User-Agent header via the optional getUserAgentInfo() |
|
302 |
- // method |
|
303 |
- $clientUAInfo = null; |
|
304 |
- if (method_exists($this->httpClient(), 'getUserAgentInfo')) { |
|
305 |
- $clientUAInfo = $this->httpClient()->getUserAgentInfo(); |
|
306 |
- } |
|
307 |
- |
|
308 |
- $absUrl = $this->_apiBase.$url; |
|
309 |
- $params = self::_encodeObjects($params); |
|
310 |
- $defaultHeaders = $this->_defaultHeaders($myApiKey, $clientUAInfo); |
|
311 |
- if (Telnyx::$apiVersion) { |
|
312 |
- $defaultHeaders['Telnyx-Version'] = Telnyx::$apiVersion; |
|
313 |
- } |
|
314 |
- |
|
315 |
- if (Telnyx::$accountId) { |
|
316 |
- $defaultHeaders['Telnyx-Account'] = Telnyx::$accountId; |
|
317 |
- } |
|
318 |
- |
|
319 |
- if (Telnyx::$enableTelemetry && self::$requestTelemetry != null) { |
|
320 |
- $defaultHeaders["X-Telnyx-Client-Telemetry"] = self::_telemetryJson(self::$requestTelemetry); |
|
321 |
- } |
|
322 |
- |
|
323 |
- $hasFile = false; |
|
324 |
- $hasCurlFile = class_exists('\CURLFile', false); |
|
325 |
- foreach ($params as $k => $v) { |
|
326 |
- if (is_resource($v)) { |
|
327 |
- $hasFile = true; |
|
328 |
- $params[$k] = self::_processResourceParam($v, $hasCurlFile); |
|
329 |
- } elseif ($hasCurlFile && $v instanceof \CURLFile) { |
|
330 |
- $hasFile = true; |
|
331 |
- } |
|
332 |
- } |
|
333 |
- |
|
334 |
- if ($hasFile) { |
|
335 |
- $defaultHeaders['Content-Type'] = 'multipart/form-data'; |
|
336 |
- } else { |
|
337 |
- $defaultHeaders['Content-Type'] = 'application/json'; |
|
338 |
- } |
|
339 |
- |
|
340 |
- $combinedHeaders = array_merge($defaultHeaders, $headers); |
|
341 |
- $rawHeaders = []; |
|
342 |
- |
|
343 |
- foreach ($combinedHeaders as $header => $value) { |
|
344 |
- $rawHeaders[] = $header . ': ' . $value; |
|
345 |
- } |
|
346 |
- |
|
347 |
- $requestStartMs = Util\Util::currentTimeMillis(); |
|
348 |
- |
|
349 |
- list($rbody, $rcode, $rheaders) = $this->httpClient()->request( |
|
350 |
- $method, |
|
351 |
- $absUrl, |
|
352 |
- $rawHeaders, |
|
353 |
- $params, |
|
354 |
- $hasFile |
|
355 |
- ); |
|
356 |
- |
|
357 |
-// if (array_key_exists('request-id', $rheaders)) { |
|
358 |
- if (property_exists($rheaders, 'request-id') && (null !== $rheaders->request-id)) { |
|
359 |
- self::$requestTelemetry = new RequestTelemetry( |
|
360 |
- $rheaders['request-id'], |
|
361 |
- Util\Util::currentTimeMillis() - $requestStartMs |
|
362 |
- ); |
|
363 |
- } |
|
364 |
- |
|
365 |
- return [$rbody, $rcode, $rheaders, $myApiKey]; |
|
366 |
- } |
|
367 |
- |
|
368 |
- /** |
|
369 |
- * @param resource $resource |
|
370 |
- * @param bool $hasCurlFile |
|
371 |
- * |
|
372 |
- * @return \CURLFile|string |
|
373 |
- * @throws Error\Api |
|
374 |
- */ |
|
375 |
- private function _processResourceParam($resource, $hasCurlFile) |
|
376 |
- { |
|
377 |
- if (get_resource_type($resource) !== 'stream') { |
|
378 |
- throw new Error\Api( |
|
379 |
- 'Attempted to upload a resource that is not a stream' |
|
380 |
- ); |
|
381 |
- } |
|
382 |
- |
|
383 |
- $metaData = stream_get_meta_data($resource); |
|
384 |
- if ($metaData['wrapper_type'] !== 'plainfile') { |
|
385 |
- throw new Error\Api( |
|
386 |
- 'Only plainfile resource streams are supported' |
|
387 |
- ); |
|
388 |
- } |
|
389 |
- |
|
390 |
- if ($hasCurlFile) { |
|
391 |
- // We don't have the filename or mimetype, but the API doesn't care |
|
392 |
- return new \CURLFile($metaData['uri']); |
|
393 |
- } else { |
|
394 |
- return '@'.$metaData['uri']; |
|
395 |
- } |
|
396 |
- } |
|
397 |
- |
|
398 |
- /** |
|
399 |
- * @param string $rbody |
|
400 |
- * @param int $rcode |
|
401 |
- * @param array $rheaders |
|
402 |
- * |
|
403 |
- * @return mixed |
|
404 |
- * @throws Error\Api |
|
405 |
- * @throws Error\Authentication |
|
406 |
- * @throws Error\Card |
|
407 |
- * @throws Error\InvalidRequest |
|
408 |
- * @throws Error\Permission |
|
409 |
- * @throws Error\RateLimit |
|
410 |
- * @throws Error\Idempotency |
|
411 |
- */ |
|
412 |
- private function _interpretResponse($rbody, $rcode, $rheaders) |
|
413 |
- { |
|
414 |
- $resp = json_decode($rbody, true); |
|
415 |
- |
|
416 |
- // Move [data] to the parent node |
|
417 |
- if (isset($resp['data'])) { |
|
418 |
- $resp = $resp['data']; |
|
419 |
- } |
|
420 |
- |
|
421 |
- $jsonError = json_last_error(); |
|
422 |
- if ($resp === null && $jsonError !== JSON_ERROR_NONE) { |
|
423 |
- $msg = "Invalid response body from API: $rbody " |
|
424 |
- . "(HTTP response code was $rcode, json_last_error() was $jsonError)"; |
|
425 |
- throw new Error\Api($msg, $rcode, $rbody); |
|
426 |
- } |
|
427 |
- |
|
428 |
- if ($rcode < 200 || $rcode >= 300) { |
|
429 |
- $this->handleErrorResponse($rbody, $rcode, $rheaders, $resp); |
|
430 |
- } |
|
431 |
- return $resp; |
|
432 |
- } |
|
433 |
- |
|
434 |
- /** |
|
435 |
- * @static |
|
436 |
- * |
|
437 |
- * @param HttpClient\ClientInterface $client |
|
438 |
- */ |
|
439 |
- public static function setHttpClient($client) |
|
440 |
- { |
|
441 |
- self::$_httpClient = $client; |
|
442 |
- } |
|
443 |
- |
|
444 |
- /** |
|
445 |
- * @static |
|
446 |
- * |
|
447 |
- * Resets any stateful telemetry data |
|
448 |
- */ |
|
449 |
- public static function resetTelemetry() |
|
450 |
- { |
|
451 |
- self::$requestTelemetry = null; |
|
452 |
- } |
|
453 |
- |
|
454 |
- /** |
|
455 |
- * @return HttpClient\ClientInterface |
|
456 |
- */ |
|
457 |
- private function httpClient() |
|
458 |
- { |
|
459 |
- if (!self::$_httpClient) { |
|
460 |
- self::$_httpClient = HttpClient\CurlClient::instance(); |
|
461 |
- } |
|
462 |
- return self::$_httpClient; |
|
463 |
- } |
|
464 |
-} |
1 | 1 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,464 @@ |
1 |
+<?php |
|
2 |
+ |
|
3 |
+namespace Telnyx; |
|
4 |
+ |
|
5 |
+/** |
|
6 |
+ * Class ApiRequestor |
|
7 |
+ * |
|
8 |
+ * @package Telnyx |
|
9 |
+ */ |
|
10 |
+class ApiRequestor |
|
11 |
+{ |
|
12 |
+ /** |
|
13 |
+ * @var string|null |
|
14 |
+ */ |
|
15 |
+ private $_apiKey; |
|
16 |
+ |
|
17 |
+ /** |
|
18 |
+ * @var string |
|
19 |
+ */ |
|
20 |
+ private $_apiBase; |
|
21 |
+ |
|
22 |
+ /** |
|
23 |
+ * @var HttpClient\ClientInterface |
|
24 |
+ */ |
|
25 |
+ private static $_httpClient; |
|
26 |
+ |
|
27 |
+ /** |
|
28 |
+ * @var RequestTelemetry |
|
29 |
+ */ |
|
30 |
+ private static $requestTelemetry; |
|
31 |
+ |
|
32 |
+ /** |
|
33 |
+ * ApiRequestor constructor. |
|
34 |
+ * |
|
35 |
+ * @param string|null $apiKey |
|
36 |
+ * @param string|null $apiBase |
|
37 |
+ */ |
|
38 |
+ public function __construct($apiKey = null, $apiBase = null) |
|
39 |
+ { |
|
40 |
+ $this->_apiKey = $apiKey; |
|
41 |
+ if (!$apiBase) { |
|
42 |
+ $apiBase = Telnyx::$apiBase; |
|
43 |
+ } |
|
44 |
+ $this->_apiBase = $apiBase; |
|
45 |
+ } |
|
46 |
+ |
|
47 |
+ /** |
|
48 |
+ * Creates a telemetry json blob for use in 'X-Telnyx-Client-Telemetry' headers |
|
49 |
+ * @static |
|
50 |
+ * |
|
51 |
+ * @param RequestTelemetry $requestTelemetry |
|
52 |
+ * @return string |
|
53 |
+ */ |
|
54 |
+ private static function _telemetryJson($requestTelemetry) |
|
55 |
+ { |
|
56 |
+ $payload = array( |
|
57 |
+ 'last_request_metrics' => array( |
|
58 |
+ 'request_id' => $requestTelemetry->requestId, |
|
59 |
+ 'request_duration_ms' => $requestTelemetry->requestDuration, |
|
60 |
+ )); |
|
61 |
+ |
|
62 |
+ $result = json_encode($payload); |
|
63 |
+ if ($result != false) { |
|
64 |
+ return $result; |
|
65 |
+ } else { |
|
66 |
+ Telnyx::getLogger()->error("Serializing telemetry payload failed!"); |
|
67 |
+ return "{}"; |
|
68 |
+ } |
|
69 |
+ } |
|
70 |
+ |
|
71 |
+ /** |
|
72 |
+ * @static |
|
73 |
+ * |
|
74 |
+ * @param ApiResource|bool|array|mixed $d |
|
75 |
+ * |
|
76 |
+ * @return ApiResource|array|string|mixed |
|
77 |
+ */ |
|
78 |
+ private static function _encodeObjects($d) |
|
79 |
+ { |
|
80 |
+ if ($d instanceof ApiResource) { |
|
81 |
+ return Util\Util::utf8($d->id); |
|
82 |
+ } elseif ($d === true) { |
|
83 |
+ return 'true'; |
|
84 |
+ } elseif ($d === false) { |
|
85 |
+ return 'false'; |
|
86 |
+ } elseif (is_array($d)) { |
|
87 |
+ $res = []; |
|
88 |
+ foreach ($d as $k => $v) { |
|
89 |
+ $res[$k] = self::_encodeObjects($v); |
|
90 |
+ } |
|
91 |
+ return $res; |
|
92 |
+ } else { |
|
93 |
+ return Util\Util::utf8($d); |
|
94 |
+ } |
|
95 |
+ } |
|
96 |
+ |
|
97 |
+ /** |
|
98 |
+ * @param string $method |
|
99 |
+ * @param string $url |
|
100 |
+ * @param array|null $params |
|
101 |
+ * @param array|null $headers |
|
102 |
+ * |
|
103 |
+ * @return array An array whose first element is an API response and second |
|
104 |
+ * element is the API key used to make the request. |
|
105 |
+ * @throws Error\Api |
|
106 |
+ * @throws Error\Authentication |
|
107 |
+ * @throws Error\Card |
|
108 |
+ * @throws Error\InvalidRequest |
|
109 |
+ * @throws Error\Permission |
|
110 |
+ * @throws Error\RateLimit |
|
111 |
+ * @throws Error\Idempotency |
|
112 |
+ * @throws Error\ApiConnection |
|
113 |
+ */ |
|
114 |
+ public function request($method, $url, $params = null, $headers = null) |
|
115 |
+ { |
|
116 |
+ $params = $params ?: []; |
|
117 |
+ $headers = $headers ?: []; |
|
118 |
+ list($rbody, $rcode, $rheaders, $myApiKey) = |
|
119 |
+ $this->_requestRaw($method, $url, $params, $headers); |
|
120 |
+ $json = $this->_interpretResponse($rbody, $rcode, $rheaders); |
|
121 |
+ $resp = new ApiResponse($rbody, $rcode, $rheaders, $json); |
|
122 |
+ return [$resp, $myApiKey]; |
|
123 |
+ } |
|
124 |
+ |
|
125 |
+ /** |
|
126 |
+ * @param string $rbody A JSON string. |
|
127 |
+ * @param int $rcode |
|
128 |
+ * @param array $rheaders |
|
129 |
+ * @param array $resp |
|
130 |
+ * |
|
131 |
+ * @throws Error\InvalidRequest if the error is caused by the user. |
|
132 |
+ * @throws Error\Authentication if the error is caused by a lack of |
|
133 |
+ * permissions. |
|
134 |
+ * @throws Error\Permission if the error is caused by insufficient |
|
135 |
+ * permissions. |
|
136 |
+ * @throws Error\Card if the error is the error code is 402 (payment |
|
137 |
+ * required) |
|
138 |
+ * @throws Error\InvalidRequest if the error is caused by the user. |
|
139 |
+ * @throws Error\Idempotency if the error is caused by an idempotency key. |
|
140 |
+ * @throws Error\Permission if the error is caused by insufficient |
|
141 |
+ * permissions. |
|
142 |
+ * @throws Error\RateLimit if the error is caused by too many requests |
|
143 |
+ * hitting the API. |
|
144 |
+ * @throws Error\Api otherwise. |
|
145 |
+ */ |
|
146 |
+ public function handleErrorResponse($rbody, $rcode, $rheaders, $resp) |
|
147 |
+ { |
|
148 |
+ if (!is_array($resp) || !isset($resp['error'])) { |
|
149 |
+ $msg = "Invalid response object from API: $rbody " |
|
150 |
+ . "(HTTP response code was $rcode)"; |
|
151 |
+ throw new Error\Api($msg, $rcode, $rbody, $resp, $rheaders); |
|
152 |
+ } |
|
153 |
+ |
|
154 |
+ $errorData = $resp['error']; |
|
155 |
+ |
|
156 |
+ #echo $rbody;exit; |
|
157 |
+ |
|
158 |
+ $error = null; |
|
159 |
+ if (!$error) { |
|
160 |
+ $error = self::_specificAPIError($rbody, $rcode, $rheaders, $resp, $errorData); |
|
161 |
+ } |
|
162 |
+ |
|
163 |
+ throw $error; |
|
164 |
+ } |
|
165 |
+ |
|
166 |
+ /** |
|
167 |
+ * @static |
|
168 |
+ * |
|
169 |
+ * @param string $rbody |
|
170 |
+ * @param int $rcode |
|
171 |
+ * @param array $rheaders |
|
172 |
+ * @param array $resp |
|
173 |
+ * @param array $errorData |
|
174 |
+ * |
|
175 |
+ * @return Error\RateLimit|Error\Idempotency|Error\InvalidRequest|Error\Authentication|Error\Card|Error\Permission|Error\Api |
|
176 |
+ */ |
|
177 |
+ private static function _specificAPIError($rbody, $rcode, $rheaders, $resp, $errorData) |
|
178 |
+ { |
|
179 |
+ $msg = isset($errorData['message']) ? $errorData['message'] : null; |
|
180 |
+ $param = isset($errorData['param']) ? $errorData['param'] : null; |
|
181 |
+ $code = isset($errorData['code']) ? $errorData['code'] : null; |
|
182 |
+ $type = isset($errorData['type']) ? $errorData['type'] : null; |
|
183 |
+ |
|
184 |
+ switch ($rcode) { |
|
185 |
+ case 400: |
|
186 |
+ // 'rate_limit' code is deprecated, but left here for backwards compatibility |
|
187 |
+ // for API versions earlier than 2015-09-08 |
|
188 |
+ if ($code == 'rate_limit') { |
|
189 |
+ return new Error\RateLimit($msg, $param, $rcode, $rbody, $resp, $rheaders); |
|
190 |
+ } |
|
191 |
+ if ($type == 'idempotency_error') { |
|
192 |
+ return new Error\Idempotency($msg, $rcode, $rbody, $resp, $rheaders); |
|
193 |
+ } |
|
194 |
+ |
|
195 |
+ // intentional fall-through |
|
196 |
+ // no break |
|
197 |
+ case 404: |
|
198 |
+ return new Error\InvalidRequest($msg, $param, $rcode, $rbody, $resp, $rheaders); |
|
199 |
+ case 401: |
|
200 |
+ return new Error\Authentication($msg, $rcode, $rbody, $resp, $rheaders); |
|
201 |
+ case 402: |
|
202 |
+ return new Error\Card($msg, $param, $code, $rcode, $rbody, $resp, $rheaders); |
|
203 |
+ case 403: |
|
204 |
+ return new Error\Permission($msg, $rcode, $rbody, $resp, $rheaders); |
|
205 |
+ case 429: |
|
206 |
+ return new Error\RateLimit($msg, $param, $rcode, $rbody, $resp, $rheaders); |
|
207 |
+ default: |
|
208 |
+ return new Error\Api($msg, $rcode, $rbody, $resp, $rheaders); |
|
209 |
+ } |
|
210 |
+ } |
|
211 |
+ |
|
212 |
+ /** |
|
213 |
+ * @static |
|
214 |
+ * |
|
215 |
+ * @param null|array $appInfo |
|
216 |
+ * |
|
217 |
+ * @return null|string |
|
218 |
+ */ |
|
219 |
+ private static function _formatAppInfo($appInfo) |
|
220 |
+ { |
|
221 |
+ if ($appInfo !== null) { |
|
222 |
+ $string = $appInfo['name']; |
|
223 |
+ if ($appInfo['version'] !== null) { |
|
224 |
+ $string .= '/' . $appInfo['version']; |
|
225 |
+ } |
|
226 |
+ if ($appInfo['url'] !== null) { |
|
227 |
+ $string .= ' (' . $appInfo['url'] . ')'; |
|
228 |
+ } |
|
229 |
+ return $string; |
|
230 |
+ } else { |
|
231 |
+ return null; |
|
232 |
+ } |
|
233 |
+ } |
|
234 |
+ |
|
235 |
+ /** |
|
236 |
+ * @static |
|
237 |
+ * |
|
238 |
+ * @param string $apiKey |
|
239 |
+ * @param null $clientInfo |
|
240 |
+ * |
|
241 |
+ * @return array |
|
242 |
+ */ |
|
243 |
+ private static function _defaultHeaders($apiKey, $clientInfo = null) |
|
244 |
+ { |
|
245 |
+ $uaString = 'Telnyx/v2 PhpBindings/' . Telnyx::VERSION; |
|
246 |
+ |
|
247 |
+ $langVersion = phpversion(); |
|
248 |
+ $uname = php_uname(); |
|
249 |
+ |
|
250 |
+ $appInfo = Telnyx::getAppInfo(); |
|
251 |
+ $ua = [ |
|
252 |
+ 'bindings_version' => Telnyx::VERSION, |
|
253 |
+ 'lang' => 'php', |
|
254 |
+ 'lang_version' => $langVersion, |
|
255 |
+ 'publisher' => 'telnyx', |
|
256 |
+ 'uname' => $uname, |
|
257 |
+ ]; |
|
258 |
+ if ($clientInfo) { |
|
259 |
+ $ua = array_merge($clientInfo, $ua); |
|
260 |
+ } |
|
261 |
+ if ($appInfo !== null) { |
|
262 |
+ $uaString .= ' ' . self::_formatAppInfo($appInfo); |
|
263 |
+ $ua['application'] = $appInfo; |
|
264 |
+ } |
|
265 |
+ |
|
266 |
+ $defaultHeaders = [ |
|
267 |
+ 'X-Telnyx-Client-User-Agent' => json_encode($ua), |
|
268 |
+ 'User-Agent' => $uaString, |
|
269 |
+ 'Authorization' => 'Bearer ' . $apiKey, |
|
270 |
+ ]; |
|
271 |
+ return $defaultHeaders; |
|
272 |
+ } |
|
273 |
+ |
|
274 |
+ /** |
|
275 |
+ * @param string $method |
|
276 |
+ * @param string $url |
|
277 |
+ * @param array $params |
|
278 |
+ * @param array $headers |
|
279 |
+ * |
|
280 |
+ * @return array |
|
281 |
+ * @throws Error\Api |
|
282 |
+ * @throws Error\ApiConnection |
|
283 |
+ * @throws Error\Authentication |
|
284 |
+ */ |
|
285 |
+ private function _requestRaw($method, $url, $params, $headers) |
|
286 |
+ { |
|
287 |
+ $myApiKey = $this->_apiKey; |
|
288 |
+ if (!$myApiKey) { |
|
289 |
+ $myApiKey = Telnyx::$apiKey; |
|
290 |
+ } |
|
291 |
+ |
|
292 |
+ if (!$myApiKey) { |
|
293 |
+ $msg = 'No API key provided. (HINT: set your API key using ' |
|
294 |
+ . '"Telnyx::setApiKey(<API-KEY>)". You can generate API keys from ' |
|
295 |
+ . 'the Telnyx web interface. See https://developers.telnyx.com/docs/v2/development/authentication ' |
|
296 |
+ . 'for details, or email support@telnyx.com if you have any questions.'; |
|
297 |
+ throw new Error\Authentication($msg); |
|
298 |
+ } |
|
299 |
+ |
|
300 |
+ // Clients can supply arbitrary additional keys to be included in the |
|
301 |
+ // X-Telnyx-Client-User-Agent header via the optional getUserAgentInfo() |
|
302 |
+ // method |
|
303 |
+ $clientUAInfo = null; |
|
304 |
+ if (method_exists($this->httpClient(), 'getUserAgentInfo')) { |
|
305 |
+ $clientUAInfo = $this->httpClient()->getUserAgentInfo(); |
|
306 |
+ } |
|
307 |
+ |
|
308 |
+ $absUrl = $this->_apiBase.$url; |
|
309 |
+ $params = self::_encodeObjects($params); |
|
310 |
+ $defaultHeaders = $this->_defaultHeaders($myApiKey, $clientUAInfo); |
|
311 |
+ if (Telnyx::$apiVersion) { |
|
312 |
+ $defaultHeaders['Telnyx-Version'] = Telnyx::$apiVersion; |
|
313 |
+ } |
|
314 |
+ |
|
315 |
+ if (Telnyx::$accountId) { |
|
316 |
+ $defaultHeaders['Telnyx-Account'] = Telnyx::$accountId; |
|
317 |
+ } |
|
318 |
+ |
|
319 |
+ if (Telnyx::$enableTelemetry && self::$requestTelemetry != null) { |
|
320 |
+ $defaultHeaders["X-Telnyx-Client-Telemetry"] = self::_telemetryJson(self::$requestTelemetry); |
|
321 |
+ } |
|
322 |
+ |
|
323 |
+ $hasFile = false; |
|
324 |
+ $hasCurlFile = class_exists('\CURLFile', false); |
|
325 |
+ foreach ($params as $k => $v) { |
|
326 |
+ if (is_resource($v)) { |
|
327 |
+ $hasFile = true; |
|
328 |
+ $params[$k] = self::_processResourceParam($v, $hasCurlFile); |
|
329 |
+ } elseif ($hasCurlFile && $v instanceof \CURLFile) { |
|
330 |
+ $hasFile = true; |
|
331 |
+ } |
|
332 |
+ } |
|
333 |
+ |
|
334 |
+ if ($hasFile) { |
|
335 |
+ $defaultHeaders['Content-Type'] = 'multipart/form-data'; |
|
336 |
+ } else { |
|
337 |
+ $defaultHeaders['Content-Type'] = 'application/json'; |
|
338 |
+ } |
|
339 |
+ |
|
340 |
+ $combinedHeaders = array_merge($defaultHeaders, $headers); |
|
341 |
+ $rawHeaders = []; |
|
342 |
+ |
|
343 |
+ foreach ($combinedHeaders as $header => $value) { |
|
344 |
+ $rawHeaders[] = $header . ': ' . $value; |
|
345 |
+ } |
|
346 |
+ |
|
347 |
+ $requestStartMs = Util\Util::currentTimeMillis(); |
|
348 |
+ |
|
349 |
+ list($rbody, $rcode, $rheaders) = $this->httpClient()->request( |
|
350 |
+ $method, |
|
351 |
+ $absUrl, |
|
352 |
+ $rawHeaders, |
|
353 |
+ $params, |
|
354 |
+ $hasFile |
|
355 |
+ ); |
|
356 |
+ |
|
357 |
+// if (array_key_exists('request-id', $rheaders)) { |
|
358 |
+ if (property_exists($rheaders, 'request-id') && (null !== $rheaders->request-id)) { |
|
359 |
+ self::$requestTelemetry = new RequestTelemetry( |
|
360 |
+ $rheaders['request-id'], |
|
361 |
+ Util\Util::currentTimeMillis() - $requestStartMs |
|
362 |
+ ); |
|
363 |
+ } |
|
364 |
+ |
|
365 |
+ return [$rbody, $rcode, $rheaders, $myApiKey]; |
|
366 |
+ } |
|
367 |
+ |
|
368 |
+ /** |
|
369 |
+ * @param resource $resource |
|
370 |
+ * @param bool $hasCurlFile |
|
371 |
+ * |
|
372 |
+ * @return \CURLFile|string |
|
373 |
+ * @throws Error\Api |
|
374 |
+ */ |
|
375 |
+ private function _processResourceParam($resource, $hasCurlFile) |
|
376 |
+ { |
|
377 |
+ if (get_resource_type($resource) !== 'stream') { |
|
378 |
+ throw new Error\Api( |
|
379 |
+ 'Attempted to upload a resource that is not a stream' |
|
380 |
+ ); |
|
381 |
+ } |
|
382 |
+ |
|
383 |
+ $metaData = stream_get_meta_data($resource); |
|
384 |
+ if ($metaData['wrapper_type'] !== 'plainfile') { |
|
385 |
+ throw new Error\Api( |
|
386 |
+ 'Only plainfile resource streams are supported' |
|
387 |
+ ); |
|
388 |
+ } |
|
389 |
+ |
|
390 |
+ if ($hasCurlFile) { |
|
391 |
+ // We don't have the filename or mimetype, but the API doesn't care |
|
392 |
+ return new \CURLFile($metaData['uri']); |
|
393 |
+ } else { |
|
394 |
+ return '@'.$metaData['uri']; |
|
395 |
+ } |
|
396 |
+ } |
|
397 |
+ |
|
398 |
+ /** |
|
399 |
+ * @param string $rbody |
|
400 |
+ * @param int $rcode |
|
401 |
+ * @param array $rheaders |
|
402 |
+ * |
|
403 |
+ * @return mixed |
|
404 |
+ * @throws Error\Api |
|
405 |
+ * @throws Error\Authentication |
|
406 |
+ * @throws Error\Card |
|
407 |
+ * @throws Error\InvalidRequest |
|
408 |
+ * @throws Error\Permission |
|
409 |
+ * @throws Error\RateLimit |
|
410 |
+ * @throws Error\Idempotency |
|
411 |
+ */ |
|
412 |
+ private function _interpretResponse($rbody, $rcode, $rheaders) |
|
413 |
+ { |
|
414 |
+ $resp = json_decode($rbody, true); |
|
415 |
+ |
|
416 |
+ // Move [data] to the parent node |
|
417 |
+ if (isset($resp['data'])) { |
|
418 |
+ $resp = $resp['data']; |
|
419 |
+ } |
|
420 |
+ |
|
421 |
+ $jsonError = json_last_error(); |
|
422 |
+ if ($resp === null && $jsonError !== JSON_ERROR_NONE) { |
|
423 |
+ $msg = "Invalid response body from API: $rbody " |
|
424 |
+ . "(HTTP response code was $rcode, json_last_error() was $jsonError)"; |
|
425 |
+ throw new Error\Api($msg, $rcode, $rbody); |
|
426 |
+ } |
|
427 |
+ |
|
428 |
+ if ($rcode < 200 || $rcode >= 300) { |
|
429 |
+ $this->handleErrorResponse($rbody, $rcode, $rheaders, $resp); |
|
430 |
+ } |
|
431 |
+ return $resp; |
|
432 |
+ } |
|
433 |
+ |
|
434 |
+ /** |
|
435 |
+ * @static |
|
436 |
+ * |
|
437 |
+ * @param HttpClient\ClientInterface $client |
|
438 |
+ */ |
|
439 |
+ public static function setHttpClient($client) |
|
440 |
+ { |
|
441 |
+ self::$_httpClient = $client; |
|
442 |
+ } |
|
443 |
+ |
|
444 |
+ /** |
|
445 |
+ * @static |
|
446 |
+ * |
|
447 |
+ * Resets any stateful telemetry data |
|
448 |
+ */ |
|
449 |
+ public static function resetTelemetry() |
|
450 |
+ { |
|
451 |
+ self::$requestTelemetry = null; |
|
452 |
+ } |
|
453 |
+ |
|
454 |
+ /** |
|
455 |
+ * @return HttpClient\ClientInterface |
|
456 |
+ */ |
|
457 |
+ private function httpClient() |
|
458 |
+ { |
|
459 |
+ if (!self::$_httpClient) { |
|
460 |
+ self::$_httpClient = HttpClient\CurlClient::instance(); |
|
461 |
+ } |
|
462 |
+ return self::$_httpClient; |
|
463 |
+ } |
|
464 |
+} |
1 | 1 |
deleted file mode 100644 |
... | ... |
@@ -1,464 +0,0 @@ |
1 |
-<?php |
|
2 |
- |
|
3 |
-namespace Telnyx; |
|
4 |
- |
|
5 |
-/** |
|
6 |
- * Class ApiRequestor |
|
7 |
- * |
|
8 |
- * @package Telnyx |
|
9 |
- */ |
|
10 |
-class ApiRequestor |
|
11 |
-{ |
|
12 |
- /** |
|
13 |
- * @var string|null |
|
14 |
- */ |
|
15 |
- private $_apiKey; |
|
16 |
- |
|
17 |
- /** |
|
18 |
- * @var string |
|
19 |
- */ |
|
20 |
- private $_apiBase; |
|
21 |
- |
|
22 |
- /** |
|
23 |
- * @var HttpClient\ClientInterface |
|
24 |
- */ |
|
25 |
- private static $_httpClient; |
|
26 |
- |
|
27 |
- /** |
|
28 |
- * @var RequestTelemetry |
|
29 |
- */ |
|
30 |
- private static $requestTelemetry; |
|
31 |
- |
|
32 |
- /** |
|
33 |
- * ApiRequestor constructor. |
|
34 |
- * |
|
35 |
- * @param string|null $apiKey |
|
36 |
- * @param string|null $apiBase |
|
37 |
- */ |
|
38 |
- public function __construct($apiKey = null, $apiBase = null) |
|
39 |
- { |
|
40 |
- $this->_apiKey = $apiKey; |
|
41 |
- if (!$apiBase) { |
|
42 |
- $apiBase = Telnyx::$apiBase; |
|
43 |
- } |
|
44 |
- $this->_apiBase = $apiBase; |
|
45 |
- } |
|
46 |
- |
|
47 |
- /** |
|
48 |
- * Creates a telemetry json blob for use in 'X-Telnyx-Client-Telemetry' headers |
|
49 |
- * @static |
|
50 |
- * |
|
51 |
- * @param RequestTelemetry $requestTelemetry |
|
52 |
- * @return string |
|
53 |
- */ |
|
54 |
- private static function _telemetryJson($requestTelemetry) |
|
55 |
- { |
|
56 |
- $payload = array( |
|
57 |
- 'last_request_metrics' => array( |
|
58 |
- 'request_id' => $requestTelemetry->requestId, |
|
59 |
- 'request_duration_ms' => $requestTelemetry->requestDuration, |
|
60 |
- )); |
|
61 |
- |
|
62 |
- $result = json_encode($payload); |
|
63 |
- if ($result != false) { |
|
64 |
- return $result; |
|
65 |
- } else { |
|
66 |
- Telnyx::getLogger()->error("Serializing telemetry payload failed!"); |
|
67 |
- return "{}"; |
|
68 |
- } |
|
69 |
- } |
|
70 |
- |
|
71 |
- /** |
|
72 |
- * @static |
|
73 |
- * |
|
74 |
- * @param ApiResource|bool|array|mixed $d |
|
75 |
- * |
|
76 |
- * @return ApiResource|array|string|mixed |
|
77 |
- */ |
|
78 |
- private static function _encodeObjects($d) |
|
79 |
- { |
|
80 |
- if ($d instanceof ApiResource) { |
|
81 |
- return Util\Util::utf8($d->id); |
|
82 |
- } elseif ($d === true) { |
|
83 |
- return 'true'; |
|
84 |
- } elseif ($d === false) { |
|
85 |
- return 'false'; |
|
86 |
- } elseif (is_array($d)) { |
|
87 |
- $res = []; |
|
88 |
- foreach ($d as $k => $v) { |
|
89 |
- $res[$k] = self::_encodeObjects($v); |
|
90 |
- } |
|
91 |
- return $res; |
|
92 |
- } else { |
|
93 |
- return Util\Util::utf8($d); |
|
94 |
- } |
|
95 |
- } |
|
96 |
- |
|
97 |
- /** |
|
98 |
- * @param string $method |
|
99 |
- * @param string $url |
|
100 |
- * @param array|null $params |
|
101 |
- * @param array|null $headers |
|
102 |
- * |
|
103 |
- * @return array An array whose first element is an API response and second |
|
104 |
- * element is the API key used to make the request. |
|
105 |
- * @throws Error\Api |
|
106 |
- * @throws Error\Authentication |
|
107 |
- * @throws Error\Card |
|
108 |
- * @throws Error\InvalidRequest |
|
109 |
- * @throws Error\Permission |
|
110 |
- * @throws Error\RateLimit |
|
111 |
- * @throws Error\Idempotency |
|
112 |
- * @throws Error\ApiConnection |
|
113 |
- */ |
|
114 |
- public function request($method, $url, $params = null, $headers = null) |
|
115 |
- { |
|
116 |
- $params = $params ?: []; |
|
117 |
- $headers = $headers ?: []; |
|
118 |
- list($rbody, $rcode, $rheaders, $myApiKey) = |
|
119 |
- $this->_requestRaw($method, $url, $params, $headers); |
|
120 |
- $json = $this->_interpretResponse($rbody, $rcode, $rheaders); |
|
121 |
- $resp = new ApiResponse($rbody, $rcode, $rheaders, $json); |
|
122 |
- return [$resp, $myApiKey]; |
|
123 |
- } |
|
124 |
- |
|
125 |
- /** |
|
126 |
- * @param string $rbody A JSON string. |
|
127 |
- * @param int $rcode |
|
128 |
- * @param array $rheaders |
|
129 |
- * @param array $resp |
|
130 |
- * |
|
131 |
- * @throws Error\InvalidRequest if the error is caused by the user. |
|
132 |
- * @throws Error\Authentication if the error is caused by a lack of |
|
133 |
- * permissions. |
|
134 |
- * @throws Error\Permission if the error is caused by insufficient |
|
135 |
- * permissions. |
|
136 |
- * @throws Error\Card if the error is the error code is 402 (payment |
|
137 |
- * required) |
|
138 |
- * @throws Error\InvalidRequest if the error is caused by the user. |
|
139 |
- * @throws Error\Idempotency if the error is caused by an idempotency key. |
|
140 |
- * @throws Error\Permission if the error is caused by insufficient |
|
141 |
- * permissions. |
|
142 |
- * @throws Error\RateLimit if the error is caused by too many requests |
|
143 |
- * hitting the API. |
|
144 |
- * @throws Error\Api otherwise. |
|
145 |
- */ |
|
146 |
- public function handleErrorResponse($rbody, $rcode, $rheaders, $resp) |
|
147 |
- { |
|
148 |
- if (!is_array($resp) || !isset($resp['error'])) { |
|
149 |
- $msg = "Invalid response object from API: $rbody " |
|
150 |
- . "(HTTP response code was $rcode)"; |
|
151 |
- throw new Error\Api($msg, $rcode, $rbody, $resp, $rheaders); |
|
152 |
- } |
|
153 |
- |
|
154 |
- $errorData = $resp['error']; |
|
155 |
- |
|
156 |
- #echo $rbody;exit; |
|
157 |
- |
|
158 |
- $error = null; |
|
159 |
- if (!$error) { |
|
160 |
- $error = self::_specificAPIError($rbody, $rcode, $rheaders, $resp, $errorData); |
|
161 |
- } |
|
162 |
- |
|
163 |
- throw $error; |
|
164 |
- } |
|
165 |
- |
|
166 |
- /** |
|
167 |
- * @static |
|
168 |
- * |
|
169 |
- * @param string $rbody |
|
170 |
- * @param int $rcode |
|
171 |
- * @param array $rheaders |
|
172 |
- * @param array $resp |
|
173 |
- * @param array $errorData |
|
174 |
- * |
|
175 |
- * @return Error\RateLimit|Error\Idempotency|Error\InvalidRequest|Error\Authentication|Error\Card|Error\Permission|Error\Api |
|
176 |
- */ |
|
177 |
- private static function _specificAPIError($rbody, $rcode, $rheaders, $resp, $errorData) |
|
178 |
- { |
|
179 |
- $msg = isset($errorData['message']) ? $errorData['message'] : null; |
|
180 |
- $param = isset($errorData['param']) ? $errorData['param'] : null; |
|
181 |
- $code = isset($errorData['code']) ? $errorData['code'] : null; |
|
182 |
- $type = isset($errorData['type']) ? $errorData['type'] : null; |
|
183 |
- |
|
184 |
- switch ($rcode) { |
|
185 |
- case 400: |
|
186 |
- // 'rate_limit' code is deprecated, but left here for backwards compatibility |
|
187 |
- // for API versions earlier than 2015-09-08 |
|
188 |
- if ($code == 'rate_limit') { |
|
189 |
- return new Error\RateLimit($msg, $param, $rcode, $rbody, $resp, $rheaders); |
|
190 |
- } |
|
191 |
- if ($type == 'idempotency_error') { |
|
192 |
- return new Error\Idempotency($msg, $rcode, $rbody, $resp, $rheaders); |
|
193 |
- } |
|
194 |
- |
|
195 |
- // intentional fall-through |
|
196 |
- // no break |
|
197 |
- case 404: |
|
198 |
- return new Error\InvalidRequest($msg, $param, $rcode, $rbody, $resp, $rheaders); |
|
199 |
- case 401: |
|
200 |
- return new Error\Authentication($msg, $rcode, $rbody, $resp, $rheaders); |
|
201 |
- case 402: |
|
202 |
- return new Error\Card($msg, $param, $code, $rcode, $rbody, $resp, $rheaders); |
|
203 |
- case 403: |
|
204 |
- return new Error\Permission($msg, $rcode, $rbody, $resp, $rheaders); |
|
205 |
- case 429: |
|
206 |
- return new Error\RateLimit($msg, $param, $rcode, $rbody, $resp, $rheaders); |
|
207 |
- default: |
|
208 |
- return new Error\Api($msg, $rcode, $rbody, $resp, $rheaders); |
|
209 |
- } |
|
210 |
- } |
|
211 |
- |
|
212 |
- /** |
|
213 |
- * @static |
|
214 |
- * |
|
215 |
- * @param null|array $appInfo |
|
216 |
- * |
|
217 |
- * @return null|string |
|
218 |
- */ |
|
219 |
- private static function _formatAppInfo($appInfo) |
|
220 |
- { |
|
221 |
- if ($appInfo !== null) { |
|
222 |
- $string = $appInfo['name']; |
|
223 |
- if ($appInfo['version'] !== null) { |
|
224 |
- $string .= '/' . $appInfo['version']; |
|
225 |
- } |
|
226 |
- if ($appInfo['url'] !== null) { |
|
227 |
- $string .= ' (' . $appInfo['url'] . ')'; |
|
228 |
- } |
|
229 |
- return $string; |
|
230 |
- } else { |
|
231 |
- return null; |
|
232 |
- } |
|
233 |
- } |
|
234 |
- |
|
235 |
- /** |
|
236 |
- * @static |
|
237 |
- * |
|
238 |
- * @param string $apiKey |
|
239 |
- * @param null $clientInfo |
|
240 |
- * |
|
241 |
- * @return array |
|
242 |
- */ |
|
243 |
- private static function _defaultHeaders($apiKey, $clientInfo = null) |
|
244 |
- { |
|
245 |
- $uaString = 'Telnyx/v2 PhpBindings/' . Telnyx::VERSION; |
|
246 |
- |
|
247 |
- $langVersion = phpversion(); |
|
248 |
- $uname = php_uname(); |
|
249 |
- |
|
250 |
- $appInfo = Telnyx::getAppInfo(); |
|
251 |
- $ua = [ |
|
252 |
- 'bindings_version' => Telnyx::VERSION, |
|
253 |
- 'lang' => 'php', |
|
254 |
- 'lang_version' => $langVersion, |
|
255 |
- 'publisher' => 'telnyx', |
|
256 |
- 'uname' => $uname, |
|
257 |
- ]; |
|
258 |
- if ($clientInfo) { |
|
259 |
- $ua = array_merge($clientInfo, $ua); |
|
260 |
- } |
|
261 |
- if ($appInfo !== null) { |
|
262 |
- $uaString .= ' ' . self::_formatAppInfo($appInfo); |
|
263 |
- $ua['application'] = $appInfo; |
|
264 |
- } |
|
265 |
- |
|
266 |
- $defaultHeaders = [ |
|
267 |
- 'X-Telnyx-Client-User-Agent' => json_encode($ua), |
|
268 |
- 'User-Agent' => $uaString, |
|
269 |
- 'Authorization' => 'Bearer ' . $apiKey, |
|
270 |
- ]; |
|
271 |
- return $defaultHeaders; |
|
272 |
- } |
|
273 |
- |
|
274 |
- /** |
|
275 |
- * @param string $method |
|
276 |
- * @param string $url |
|
277 |
- * @param array $params |
|
278 |
- * @param array $headers |
|
279 |
- * |
|
280 |
- * @return array |
|
281 |
- * @throws Error\Api |
|
282 |
- * @throws Error\ApiConnection |
|
283 |
- * @throws Error\Authentication |
|
284 |
- */ |
|
285 |
- private function _requestRaw($method, $url, $params, $headers) |
|
286 |
- { |
|
287 |
- $myApiKey = $this->_apiKey; |
|
288 |
- if (!$myApiKey) { |
|
289 |
- $myApiKey = Telnyx::$apiKey; |
|
290 |
- } |
|
291 |
- |
|
292 |
- if (!$myApiKey) { |
|
293 |
- $msg = 'No API key provided. (HINT: set your API key using ' |
|
294 |
- . '"Telnyx::setApiKey(<API-KEY>)". You can generate API keys from ' |
|
295 |
- . 'the Telnyx web interface. See https://developers.telnyx.com/docs/v2/development/authentication ' |
|
296 |
- . 'for details, or email support@telnyx.com if you have any questions.'; |
|
297 |
- throw new Error\Authentication($msg); |
|
298 |
- } |
|
299 |
- |
|
300 |
- // Clients can supply arbitrary additional keys to be included in the |
|
301 |
- // X-Telnyx-Client-User-Agent header via the optional getUserAgentInfo() |
|
302 |
- // method |
|
303 |
- $clientUAInfo = null; |
|
304 |
- if (method_exists($this->httpClient(), 'getUserAgentInfo')) { |
|
305 |
- $clientUAInfo = $this->httpClient()->getUserAgentInfo(); |
|
306 |
- } |
|
307 |
- |
|
308 |
- $absUrl = $this->_apiBase.$url; |
|
309 |
- $params = self::_encodeObjects($params); |
|
310 |
- $defaultHeaders = $this->_defaultHeaders($myApiKey, $clientUAInfo); |
|
311 |
- if (Telnyx::$apiVersion) { |
|
312 |
- $defaultHeaders['Telnyx-Version'] = Telnyx::$apiVersion; |
|
313 |
- } |
|
314 |
- |
|
315 |
- if (Telnyx::$accountId) { |
|
316 |
- $defaultHeaders['Telnyx-Account'] = Telnyx::$accountId; |
|
317 |
- } |
|
318 |
- |
|
319 |
- if (Telnyx::$enableTelemetry && self::$requestTelemetry != null) { |
|
320 |
- $defaultHeaders["X-Telnyx-Client-Telemetry"] = self::_telemetryJson(self::$requestTelemetry); |
|
321 |
- } |
|
322 |
- |
|
323 |
- $hasFile = false; |
|
324 |
- $hasCurlFile = class_exists('\CURLFile', false); |
|
325 |
- foreach ($params as $k => $v) { |
|
326 |
- if (is_resource($v)) { |
|
327 |
- $hasFile = true; |
|
328 |
- $params[$k] = self::_processResourceParam($v, $hasCurlFile); |
|
329 |
- } elseif ($hasCurlFile && $v instanceof \CURLFile) { |
|
330 |
- $hasFile = true; |
|
331 |
- } |
|
332 |
- } |
|
333 |
- |
|
334 |
- if ($hasFile) { |
|
335 |
- $defaultHeaders['Content-Type'] = 'multipart/form-data'; |
|
336 |
- } else { |
|
337 |
- $defaultHeaders['Content-Type'] = 'application/json'; |
|
338 |
- } |
|
339 |
- |
|
340 |
- $combinedHeaders = array_merge($defaultHeaders, $headers); |
|
341 |
- $rawHeaders = []; |
|
342 |
- |
|
343 |
- foreach ($combinedHeaders as $header => $value) { |
|
344 |
- $rawHeaders[] = $header . ': ' . $value; |
|
345 |
- } |
|
346 |
- |
|
347 |
- $requestStartMs = Util\Util::currentTimeMillis(); |
|
348 |
- |
|
349 |
- list($rbody, $rcode, $rheaders) = $this->httpClient()->request( |
|
350 |
- $method, |
|
351 |
- $absUrl, |
|
352 |
- $rawHeaders, |
|
353 |
- $params, |
|
354 |
- $hasFile |
|
355 |
- ); |
|
356 |
- |
|
357 |
-// if (array_key_exists('request-id', $rheaders)) { |
|
358 |
- if (property_exists($rheaders, 'request-id') && (null !== $rheaders->request-id)) { |
|
359 |
- self::$requestTelemetry = new RequestTelemetry( |
|
360 |
- $rheaders['request-id'], |
|
361 |
- Util\Util::currentTimeMillis() - $requestStartMs |
|
362 |
- ); |
|
363 |
- } |
|
364 |
- |
|
365 |
- return [$rbody, $rcode, $rheaders, $myApiKey]; |
|
366 |
- } |
|
367 |
- |
|
368 |
- /** |
|
369 |
- * @param resource $resource |
|
370 |
- * @param bool $hasCurlFile |
|
371 |
- * |
|
372 |
- * @return \CURLFile|string |
|
373 |
- * @throws Error\Api |
|
374 |
- */ |
|
375 |
- private function _processResourceParam($resource, $hasCurlFile) |
|
376 |
- { |
|
377 |
- if (get_resource_type($resource) !== 'stream') { |
|
378 |
- throw new Error\Api( |
|
379 |
- 'Attempted to upload a resource that is not a stream' |
|
380 |
- ); |
|
381 |
- } |
|
382 |
- |
|
383 |
- $metaData = stream_get_meta_data($resource); |
|
384 |
- if ($metaData['wrapper_type'] !== 'plainfile') { |
|
385 |
- throw new Error\Api( |
|
386 |
- 'Only plainfile resource streams are supported' |
|
387 |
- ); |
|
388 |
- } |
|
389 |
- |
|
390 |
- if ($hasCurlFile) { |
|
391 |
- // We don't have the filename or mimetype, but the API doesn't care |
|
392 |
- return new \CURLFile($metaData['uri']); |
|
393 |
- } else { |
|
394 |
- return '@'.$metaData['uri']; |
|
395 |
- } |
|
396 |
- } |
|
397 |
- |
|
398 |
- /** |
|
399 |
- * @param string $rbody |
|
400 |
- * @param int $rcode |
|
401 |
- * @param array $rheaders |
|
402 |
- * |
|
403 |
- * @return mixed |
|
404 |
- * @throws Error\Api |
|
405 |
- * @throws Error\Authentication |
|
406 |
- * @throws Error\Card |
|
407 |
- * @throws Error\InvalidRequest |
|
408 |
- * @throws Error\Permission |
|
409 |
- * @throws Error\RateLimit |
|
410 |
- * @throws Error\Idempotency |
|
411 |
- */ |
|
412 |
- private function _interpretResponse($rbody, $rcode, $rheaders) |
|
413 |
- { |
|
414 |
- $resp = json_decode($rbody, true); |
|
415 |
- |
|
416 |
- // Move [data] to the parent node |
|
417 |
- if (isset($resp['data'])) { |
|
418 |
- $resp = $resp['data']; |
|
419 |
- } |
|
420 |
- |
|
421 |
- $jsonError = json_last_error(); |
|
422 |
- if ($resp === null && $jsonError !== JSON_ERROR_NONE) { |
|
423 |
- $msg = "Invalid response body from API: $rbody " |
|
424 |
- . "(HTTP response code was $rcode, json_last_error() was $jsonError)"; |
|
425 |
- throw new Error\Api($msg, $rcode, $rbody); |
|
426 |
- } |
|
427 |
- |
|
428 |
- if ($rcode < 200 || $rcode >= 300) { |
|
429 |
- $this->handleErrorResponse($rbody, $rcode, $rheaders, $resp); |
|
430 |
- } |
|
431 |
- return $resp; |
|
432 |
- } |
|
433 |
- |
|
434 |
- /** |
|
435 |
- * @static |
|
436 |
- * |
|
437 |
- * @param HttpClient\ClientInterface $client |
|
438 |
- */ |
|
439 |
- public static function setHttpClient($client) |
|
440 |
- { |
|
441 |
- self::$_httpClient = $client; |
|
442 |
- } |
|
443 |
- |
|
444 |
- /** |
|
445 |
- * @static |
|
446 |
- * |
|
447 |
- * Resets any stateful telemetry data |
|
448 |
- */ |
|
449 |
- public static function resetTelemetry() |
|
450 |
- { |
|
451 |
- self::$requestTelemetry = null; |
|
452 |
- } |
|
453 |
- |
|
454 |
- /** |
|
455 |
- * @return HttpClient\ClientInterface |
|
456 |
- */ |
|
457 |
- private function httpClient() |
|
458 |
- { |
|
459 |
- if (!self::$_httpClient) { |
|
460 |
- self::$_httpClient = HttpClient\CurlClient::instance(); |
|
461 |
- } |
|
462 |
- return self::$_httpClient; |
|
463 |
- } |
|
464 |
-} |
1 | 1 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,464 @@ |
1 |
+<?php |
|
2 |
+ |
|
3 |
+namespace Telnyx; |
|
4 |
+ |
|
5 |
+/** |
|
6 |
+ * Class ApiRequestor |
|
7 |
+ * |
|
8 |
+ * @package Telnyx |
|
9 |
+ */ |
|
10 |
+class ApiRequestor |
|
11 |
+{ |
|
12 |
+ /** |
|
13 |
+ * @var string|null |
|
14 |
+ */ |
|
15 |
+ private $_apiKey; |
|
16 |
+ |
|
17 |
+ /** |
|
18 |
+ * @var string |
|
19 |
+ */ |
|
20 |
+ private $_apiBase; |
|
21 |
+ |
|
22 |
+ /** |
|
23 |
+ * @var HttpClient\ClientInterface |
|
24 |
+ */ |
|
25 |
+ private static $_httpClient; |
|
26 |
+ |
|
27 |
+ /** |
|
28 |
+ * @var RequestTelemetry |
|
29 |
+ */ |
|
30 |
+ private static $requestTelemetry; |
|
31 |
+ |
|
32 |
+ /** |
|
33 |
+ * ApiRequestor constructor. |
|
34 |
+ * |
|
35 |
+ * @param string|null $apiKey |
|
36 |
+ * @param string|null $apiBase |
|
37 |
+ */ |
|
38 |
+ public function __construct($apiKey = null, $apiBase = null) |
|
39 |
+ { |
|
40 |
+ $this->_apiKey = $apiKey; |
|
41 |
+ if (!$apiBase) { |
|
42 |
+ $apiBase = Telnyx::$apiBase; |
|
43 |
+ } |
|
44 |
+ $this->_apiBase = $apiBase; |
|
45 |
+ } |
|
46 |
+ |
|
47 |
+ /** |
|
48 |
+ * Creates a telemetry json blob for use in 'X-Telnyx-Client-Telemetry' headers |
|
49 |
+ * @static |
|
50 |
+ * |
|
51 |
+ * @param RequestTelemetry $requestTelemetry |
|
52 |
+ * @return string |
|
53 |
+ */ |
|
54 |
+ private static function _telemetryJson($requestTelemetry) |
|
55 |
+ { |
|
56 |
+ $payload = array( |
|
57 |
+ 'last_request_metrics' => array( |
|
58 |
+ 'request_id' => $requestTelemetry->requestId, |
|
59 |
+ 'request_duration_ms' => $requestTelemetry->requestDuration, |
|
60 |
+ )); |
|
61 |
+ |
|
62 |
+ $result = json_encode($payload); |
|
63 |
+ if ($result != false) { |
|
64 |
+ return $result; |
|
65 |
+ } else { |
|
66 |
+ Telnyx::getLogger()->error("Serializing telemetry payload failed!"); |
|
67 |
+ return "{}"; |
|
68 |
+ } |
|
69 |
+ } |
|
70 |
+ |
|
71 |
+ /** |
|
72 |
+ * @static |
|
73 |
+ * |
|
74 |
+ * @param ApiResource|bool|array|mixed $d |
|
75 |
+ * |
|
76 |
+ * @return ApiResource|array|string|mixed |
|
77 |
+ */ |
|
78 |
+ private static function _encodeObjects($d) |
|
79 |
+ { |
|
80 |
+ if ($d instanceof ApiResource) { |
|
81 |
+ return Util\Util::utf8($d->id); |
|
82 |
+ } elseif ($d === true) { |
|
83 |
+ return 'true'; |
|
84 |
+ } elseif ($d === false) { |
|
85 |
+ return 'false'; |
|
86 |
+ } elseif (is_array($d)) { |
|
87 |
+ $res = []; |
|
88 |
+ foreach ($d as $k => $v) { |
|
89 |
+ $res[$k] = self::_encodeObjects($v); |
|
90 |
+ } |
|
91 |
+ return $res; |
|
92 |
+ } else { |
|
93 |
+ return Util\Util::utf8($d); |
|
94 |
+ } |
|
95 |
+ } |
|
96 |
+ |
|
97 |
+ /** |
|
98 |
+ * @param string $method |
|
99 |
+ * @param string $url |
|
100 |
+ * @param array|null $params |
|
101 |
+ * @param array|null $headers |
|
102 |
+ * |
|
103 |
+ * @return array An array whose first element is an API response and second |
|
104 |
+ * element is the API key used to make the request. |
|
105 |
+ * @throws Error\Api |
|
106 |
+ * @throws Error\Authentication |
|
107 |
+ * @throws Error\Card |
|
108 |
+ * @throws Error\InvalidRequest |
|
109 |
+ * @throws Error\Permission |
|
110 |
+ * @throws Error\RateLimit |
|
111 |
+ * @throws Error\Idempotency |
|
112 |
+ * @throws Error\ApiConnection |
|
113 |
+ */ |
|
114 |
+ public function request($method, $url, $params = null, $headers = null) |
|
115 |
+ { |
|
116 |
+ $params = $params ?: []; |
|
117 |
+ $headers = $headers ?: []; |
|
118 |
+ list($rbody, $rcode, $rheaders, $myApiKey) = |
|
119 |
+ $this->_requestRaw($method, $url, $params, $headers); |
|
120 |
+ $json = $this->_interpretResponse($rbody, $rcode, $rheaders); |
|
121 |
+ $resp = new ApiResponse($rbody, $rcode, $rheaders, $json); |
|
122 |
+ return [$resp, $myApiKey]; |
|
123 |
+ } |
|
124 |
+ |
|
125 |
+ /** |
|
126 |
+ * @param string $rbody A JSON string. |
|
127 |
+ * @param int $rcode |
|
128 |
+ * @param array $rheaders |
|
129 |
+ * @param array $resp |
|
130 |
+ * |
|
131 |
+ * @throws Error\InvalidRequest if the error is caused by the user. |
|
132 |
+ * @throws Error\Authentication if the error is caused by a lack of |
|
133 |
+ * permissions. |
|
134 |
+ * @throws Error\Permission if the error is caused by insufficient |
|
135 |
+ * permissions. |
|
136 |
+ * @throws Error\Card if the error is the error code is 402 (payment |
|
137 |
+ * required) |
|
138 |
+ * @throws Error\InvalidRequest if the error is caused by the user. |
|
139 |
+ * @throws Error\Idempotency if the error is caused by an idempotency key. |
|
140 |
+ * @throws Error\Permission if the error is caused by insufficient |
|
141 |
+ * permissions. |
|
142 |
+ * @throws Error\RateLimit if the error is caused by too many requests |
|
143 |
+ * hitting the API. |
|
144 |
+ * @throws Error\Api otherwise. |
|
145 |
+ */ |
|
146 |
+ public function handleErrorResponse($rbody, $rcode, $rheaders, $resp) |
|
147 |
+ { |
|
148 |
+ if (!is_array($resp) || !isset($resp['error'])) { |
|
149 |
+ $msg = "Invalid response object from API: $rbody " |
|
150 |
+ . "(HTTP response code was $rcode)"; |
|
151 |
+ throw new Error\Api($msg, $rcode, $rbody, $resp, $rheaders); |
|
152 |
+ } |
|
153 |
+ |
|
154 |
+ $errorData = $resp['error']; |
|
155 |
+ |
|
156 |
+ #echo $rbody;exit; |
|
157 |
+ |
|
158 |
+ $error = null; |
|
159 |
+ if (!$error) { |
|
160 |
+ $error = self::_specificAPIError($rbody, $rcode, $rheaders, $resp, $errorData); |
|
161 |
+ } |
|
162 |
+ |
|
163 |
+ throw $error; |
|
164 |
+ } |
|
165 |
+ |
|
166 |
+ /** |
|
167 |
+ * @static |
|
168 |
+ * |
|
169 |
+ * @param string $rbody |
|
170 |
+ * @param int $rcode |
|
171 |
+ * @param array $rheaders |
|
172 |
+ * @param array $resp |
|
173 |
+ * @param array $errorData |
|
174 |
+ * |
|
175 |
+ * @return Error\RateLimit|Error\Idempotency|Error\InvalidRequest|Error\Authentication|Error\Card|Error\Permission|Error\Api |
|
176 |
+ */ |
|
177 |
+ private static function _specificAPIError($rbody, $rcode, $rheaders, $resp, $errorData) |
|
178 |
+ { |
|
179 |
+ $msg = isset($errorData['message']) ? $errorData['message'] : null; |
|
180 |
+ $param = isset($errorData['param']) ? $errorData['param'] : null; |
|
181 |
+ $code = isset($errorData['code']) ? $errorData['code'] : null; |
|
182 |
+ $type = isset($errorData['type']) ? $errorData['type'] : null; |
|
183 |
+ |
|
184 |
+ switch ($rcode) { |
|
185 |
+ case 400: |
|
186 |
+ // 'rate_limit' code is deprecated, but left here for backwards compatibility |
|
187 |
+ // for API versions earlier than 2015-09-08 |
|
188 |
+ if ($code == 'rate_limit') { |
|
189 |
+ return new Error\RateLimit($msg, $param, $rcode, $rbody, $resp, $rheaders); |
|
190 |
+ } |
|
191 |
+ if ($type == 'idempotency_error') { |
|
192 |
+ return new Error\Idempotency($msg, $rcode, $rbody, $resp, $rheaders); |
|
193 |
+ } |
|
194 |
+ |
|
195 |
+ // intentional fall-through |
|
196 |
+ // no break |
|
197 |
+ case 404: |
|
198 |
+ return new Error\InvalidRequest($msg, $param, $rcode, $rbody, $resp, $rheaders); |
|
199 |
+ case 401: |
|
200 |
+ return new Error\Authentication($msg, $rcode, $rbody, $resp, $rheaders); |
|
201 |
+ case 402: |
|
202 |
+ return new Error\Card($msg, $param, $code, $rcode, $rbody, $resp, $rheaders); |
|
203 |
+ case 403: |
|
204 |
+ return new Error\Permission($msg, $rcode, $rbody, $resp, $rheaders); |
|
205 |
+ case 429: |
|
206 |
+ return new Error\RateLimit($msg, $param, $rcode, $rbody, $resp, $rheaders); |
|
207 |
+ default: |
|
208 |
+ return new Error\Api($msg, $rcode, $rbody, $resp, $rheaders); |
|
209 |
+ } |
|
210 |
+ } |
|
211 |
+ |
|
212 |
+ /** |
|
213 |
+ * @static |
|
214 |
+ * |
|
215 |
+ * @param null|array $appInfo |
|
216 |
+ * |
|
217 |
+ * @return null|string |
|
218 |
+ */ |
|
219 |
+ private static function _formatAppInfo($appInfo) |
|
220 |
+ { |
|
221 |
+ if ($appInfo !== null) { |
|
222 |
+ $string = $appInfo['name']; |
|
223 |
+ if ($appInfo['version'] !== null) { |
|
224 |
+ $string .= '/' . $appInfo['version']; |
|
225 |
+ } |
|
226 |
+ if ($appInfo['url'] !== null) { |
|
227 |
+ $string .= ' (' . $appInfo['url'] . ')'; |
|
228 |
+ } |
|
229 |
+ return $string; |
|
230 |
+ } else { |
|
231 |
+ return null; |
|
232 |
+ } |
|
233 |
+ } |
|
234 |
+ |
|
235 |
+ /** |
|
236 |
+ * @static |
|
237 |
+ * |
|
238 |
+ * @param string $apiKey |
|
239 |
+ * @param null $clientInfo |
|
240 |
+ * |
|
241 |
+ * @return array |
|
242 |
+ */ |
|
243 |
+ private static function _defaultHeaders($apiKey, $clientInfo = null) |
|
244 |
+ { |
|
245 |
+ $uaString = 'Telnyx/v2 PhpBindings/' . Telnyx::VERSION; |
|
246 |
+ |
|
247 |
+ $langVersion = phpversion(); |
|
248 |
+ $uname = php_uname(); |
|
249 |
+ |
|
250 |
+ $appInfo = Telnyx::getAppInfo(); |
|
251 |
+ $ua = [ |
|
252 |
+ 'bindings_version' => Telnyx::VERSION, |
|
253 |
+ 'lang' => 'php', |
|
254 |
+ 'lang_version' => $langVersion, |
|
255 |
+ 'publisher' => 'telnyx', |
|
256 |
+ 'uname' => $uname, |
|
257 |
+ ]; |
|
258 |
+ if ($clientInfo) { |
|
259 |
+ $ua = array_merge($clientInfo, $ua); |
|
260 |
+ } |
|
261 |
+ if ($appInfo !== null) { |
|
262 |
+ $uaString .= ' ' . self::_formatAppInfo($appInfo); |
|
263 |
+ $ua['application'] = $appInfo; |
|
264 |
+ } |
|
265 |
+ |
|
266 |
+ $defaultHeaders = [ |
|
267 |
+ 'X-Telnyx-Client-User-Agent' => json_encode($ua), |
|
268 |
+ 'User-Agent' => $uaString, |
|
269 |
+ 'Authorization' => 'Bearer ' . $apiKey, |
|
270 |
+ ]; |
|
271 |
+ return $defaultHeaders; |
|
272 |
+ } |
|
273 |
+ |
|
274 |
+ /** |
|
275 |
+ * @param string $method |
|
276 |
+ * @param string $url |
|
277 |
+ * @param array $params |
|
278 |
+ * @param array $headers |
|
279 |
+ * |
|
280 |
+ * @return array |
|
281 |
+ * @throws Error\Api |
|
282 |
+ * @throws Error\ApiConnection |
|
283 |
+ * @throws Error\Authentication |
|
284 |
+ */ |
|
285 |
+ private function _requestRaw($method, $url, $params, $headers) |
|
286 |
+ { |
|
287 |
+ $myApiKey = $this->_apiKey; |
|
288 |
+ if (!$myApiKey) { |
|
289 |
+ $myApiKey = Telnyx::$apiKey; |
|
290 |
+ } |
|
291 |
+ |
|
292 |
+ if (!$myApiKey) { |
|
293 |
+ $msg = 'No API key provided. (HINT: set your API key using ' |
|
294 |
+ . '"Telnyx::setApiKey(<API-KEY>)". You can generate API keys from ' |
|
295 |
+ . 'the Telnyx web interface. See https://developers.telnyx.com/docs/v2/development/authentication ' |
|
296 |
+ . 'for details, or email support@telnyx.com if you have any questions.'; |
|
297 |
+ throw new Error\Authentication($msg); |
|
298 |
+ } |
|
299 |
+ |
|
300 |
+ // Clients can supply arbitrary additional keys to be included in the |
|
301 |
+ // X-Telnyx-Client-User-Agent header via the optional getUserAgentInfo() |
|
302 |
+ // method |
|
303 |
+ $clientUAInfo = null; |
|
304 |
+ if (method_exists($this->httpClient(), 'getUserAgentInfo')) { |
|
305 |
+ $clientUAInfo = $this->httpClient()->getUserAgentInfo(); |
|
306 |
+ } |
|
307 |
+ |
|
308 |
+ $absUrl = $this->_apiBase.$url; |
|
309 |
+ $params = self::_encodeObjects($params); |
|
310 |
+ $defaultHeaders = $this->_defaultHeaders($myApiKey, $clientUAInfo); |
|
311 |
+ if (Telnyx::$apiVersion) { |
|
312 |
+ $defaultHeaders['Telnyx-Version'] = Telnyx::$apiVersion; |
|
313 |
+ } |
|
314 |
+ |
|
315 |
+ if (Telnyx::$accountId) { |
|
316 |
+ $defaultHeaders['Telnyx-Account'] = Telnyx::$accountId; |
|
317 |
+ } |
|
318 |
+ |
|
319 |
+ if (Telnyx::$enableTelemetry && self::$requestTelemetry != null) { |
|
320 |
+ $defaultHeaders["X-Telnyx-Client-Telemetry"] = self::_telemetryJson(self::$requestTelemetry); |
|
321 |
+ } |
|
322 |
+ |
|
323 |
+ $hasFile = false; |
|
324 |
+ $hasCurlFile = class_exists('\CURLFile', false); |
|
325 |
+ foreach ($params as $k => $v) { |
|
326 |
+ if (is_resource($v)) { |
|
327 |
+ $hasFile = true; |
|
328 |
+ $params[$k] = self::_processResourceParam($v, $hasCurlFile); |
|
329 |
+ } elseif ($hasCurlFile && $v instanceof \CURLFile) { |
|
330 |
+ $hasFile = true; |
|
331 |
+ } |
|
332 |
+ } |
|
333 |
+ |
|
334 |
+ if ($hasFile) { |
|
335 |
+ $defaultHeaders['Content-Type'] = 'multipart/form-data'; |
|
336 |
+ } else { |
|
337 |
+ $defaultHeaders['Content-Type'] = 'application/json'; |
|
338 |
+ } |
|
339 |
+ |
|
340 |
+ $combinedHeaders = array_merge($defaultHeaders, $headers); |
|
341 |
+ $rawHeaders = []; |
|
342 |
+ |
|
343 |
+ foreach ($combinedHeaders as $header => $value) { |
|
344 |
+ $rawHeaders[] = $header . ': ' . $value; |
|
345 |
+ } |
|
346 |
+ |
|
347 |
+ $requestStartMs = Util\Util::currentTimeMillis(); |
|
348 |
+ |
|
349 |
+ list($rbody, $rcode, $rheaders) = $this->httpClient()->request( |
|
350 |
+ $method, |
|
351 |
+ $absUrl, |
|
352 |
+ $rawHeaders, |
|
353 |
+ $params, |
|
354 |
+ $hasFile |
|
355 |
+ ); |
|
356 |
+ |
|
357 |
+// if (array_key_exists('request-id', $rheaders)) { |
|
358 |
+ if (property_exists($rheaders, 'request-id') && (null !== $rheaders->request-id)) { |
|
359 |
+ self::$requestTelemetry = new RequestTelemetry( |
|
360 |
+ $rheaders['request-id'], |
|
361 |
+ Util\Util::currentTimeMillis() - $requestStartMs |
|
362 |
+ ); |
|
363 |
+ } |
|
364 |
+ |
|
365 |
+ return [$rbody, $rcode, $rheaders, $myApiKey]; |
|
366 |
+ } |
|
367 |
+ |
|
368 |
+ /** |
|
369 |
+ * @param resource $resource |
|
370 |
+ * @param bool $hasCurlFile |
|
371 |
+ * |
|
372 |
+ * @return \CURLFile|string |
|
373 |
+ * @throws Error\Api |
|
374 |
+ */ |
|
375 |
+ private function _processResourceParam($resource, $hasCurlFile) |
|
376 |
+ { |
|
377 |
+ if (get_resource_type($resource) !== 'stream') { |
|
378 |
+ throw new Error\Api( |
|
379 |
+ 'Attempted to upload a resource that is not a stream' |
|
380 |
+ ); |
|
381 |
+ } |
|
382 |
+ |
|
383 |
+ $metaData = stream_get_meta_data($resource); |
|
384 |
+ if ($metaData['wrapper_type'] !== 'plainfile') { |
|
385 |
+ throw new Error\Api( |
|
386 |
+ 'Only plainfile resource streams are supported' |
|
387 |
+ ); |
|
388 |
+ } |
|
389 |
+ |
|
390 |
+ if ($hasCurlFile) { |
|
391 |
+ // We don't have the filename or mimetype, but the API doesn't care |
|
392 |
+ return new \CURLFile($metaData['uri']); |
|
393 |
+ } else { |
|
394 |
+ return '@'.$metaData['uri']; |
|
395 |
+ } |
|
396 |
+ } |
|
397 |
+ |
|
398 |
+ /** |
|
399 |
+ * @param string $rbody |
|
400 |
+ * @param int $rcode |
|
401 |
+ * @param array $rheaders |
|
402 |
+ * |
|
403 |
+ * @return mixed |
|
404 |
+ * @throws Error\Api |
|
405 |
+ * @throws Error\Authentication |
|
406 |
+ * @throws Error\Card |
|
407 |
+ * @throws Error\InvalidRequest |
|
408 |
+ * @throws Error\Permission |
|
409 |
+ * @throws Error\RateLimit |
|
410 |
+ * @throws Error\Idempotency |
|
411 |
+ */ |
|
412 |
+ private function _interpretResponse($rbody, $rcode, $rheaders) |
|
413 |
+ { |
|
414 |
+ $resp = json_decode($rbody, true); |
|
415 |
+ |
|
416 |
+ // Move [data] to the parent node |
|
417 |
+ if (isset($resp['data'])) { |
|
418 |
+ $resp = $resp['data']; |
|
419 |
+ } |
|
420 |
+ |
|
421 |
+ $jsonError = json_last_error(); |
|
422 |
+ if ($resp === null && $jsonError !== JSON_ERROR_NONE) { |
|
423 |
+ $msg = "Invalid response body from API: $rbody " |
|
424 |
+ . "(HTTP response code was $rcode, json_last_error() was $jsonError)"; |
|
425 |
+ throw new Error\Api($msg, $rcode, $rbody); |
|
426 |
+ } |
|
427 |
+ |
|
428 |
+ if ($rcode < 200 || $rcode >= 300) { |
|
429 |
+ $this->handleErrorResponse($rbody, $rcode, $rheaders, $resp); |
|
430 |
+ } |
|
431 |
+ return $resp; |
|
432 |
+ } |
|
433 |
+ |
|
434 |
+ /** |
|
435 |
+ * @static |
|
436 |
+ * |
|
437 |
+ * @param HttpClient\ClientInterface $client |
|
438 |
+ */ |
|
439 |
+ public static function setHttpClient($client) |
|
440 |
+ { |
|
441 |
+ self::$_httpClient = $client; |
|
442 |
+ } |
|
443 |
+ |
|
444 |
+ /** |
|
445 |
+ * @static |
|
446 |
+ * |
|
447 |
+ * Resets any stateful telemetry data |
|
448 |
+ */ |
|
449 |
+ public static function resetTelemetry() |
|
450 |
+ { |
|
451 |
+ self::$requestTelemetry = null; |
|
452 |
+ } |
|
453 |
+ |
|
454 |
+ /** |
|
455 |
+ * @return HttpClient\ClientInterface |
|
456 |
+ */ |
|
457 |
+ private function httpClient() |
|
458 |
+ { |
|
459 |
+ if (!self::$_httpClient) { |
|
460 |
+ self::$_httpClient = HttpClient\CurlClient::instance(); |
|
461 |
+ } |
|
462 |
+ return self::$_httpClient; |
|
463 |
+ } |
|
464 |
+} |
1 | 1 |
deleted file mode 100644 |
... | ... |
@@ -1,464 +0,0 @@ |
1 |
-<?php |
|
2 |
- |
|
3 |
-namespace Telnyx; |
|
4 |
- |
|
5 |
-/** |
|
6 |
- * Class ApiRequestor |
|
7 |
- * |
|
8 |
- * @package Telnyx |
|
9 |
- */ |
|
10 |
-class ApiRequestor |
|
11 |
-{ |
|
12 |
- /** |
|
13 |
- * @var string|null |
|
14 |
- */ |
|
15 |
- private $_apiKey; |
|
16 |
- |
|
17 |
- /** |
|
18 |
- * @var string |
|
19 |
- */ |
|
20 |
- private $_apiBase; |
|
21 |
- |
|
22 |
- /** |
|
23 |
- * @var HttpClient\ClientInterface |
|
24 |
- */ |
|
25 |
- private static $_httpClient; |
|
26 |
- |
|
27 |
- /** |
|
28 |
- * @var RequestTelemetry |
|
29 |
- */ |
|
30 |
- private static $requestTelemetry; |
|
31 |
- |
|
32 |
- /** |
|
33 |
- * ApiRequestor constructor. |
|
34 |
- * |
|
35 |
- * @param string|null $apiKey |
|
36 |
- * @param string|null $apiBase |
|
37 |
- */ |
|
38 |
- public function __construct($apiKey = null, $apiBase = null) |
|
39 |
- { |
|
40 |
- $this->_apiKey = $apiKey; |
|
41 |
- if (!$apiBase) { |
|
42 |
- $apiBase = Telnyx::$apiBase; |
|
43 |
- } |
|
44 |
- $this->_apiBase = $apiBase; |
|
45 |
- } |
|
46 |
- |
|
47 |
- /** |
|
48 |
- * Creates a telemetry json blob for use in 'X-Telnyx-Client-Telemetry' headers |
|
49 |
- * @static |
|
50 |
- * |
|
51 |
- * @param RequestTelemetry $requestTelemetry |
|
52 |
- * @return string |
|
53 |
- */ |
|
54 |
- private static function _telemetryJson($requestTelemetry) |
|
55 |
- { |
|
56 |
- $payload = array( |
|
57 |
- 'last_request_metrics' => array( |
|
58 |
- 'request_id' => $requestTelemetry->requestId, |
|
59 |
- 'request_duration_ms' => $requestTelemetry->requestDuration, |
|
60 |
- )); |
|
61 |
- |
|
62 |
- $result = json_encode($payload); |
|
63 |
- if ($result != false) { |
|
64 |
- return $result; |
|
65 |
- } else { |
|
66 |
- Telnyx::getLogger()->error("Serializing telemetry payload failed!"); |
|
67 |
- return "{}"; |
|
68 |
- } |
|
69 |
- } |
|
70 |
- |
|
71 |
- /** |
|
72 |
- * @static |
|
73 |
- * |
|
74 |
- * @param ApiResource|bool|array|mixed $d |
|
75 |
- * |
|
76 |
- * @return ApiResource|array|string|mixed |
|
77 |
- */ |
|
78 |
- private static function _encodeObjects($d) |
|
79 |
- { |
|
80 |
- if ($d instanceof ApiResource) { |
|
81 |
- return Util\Util::utf8($d->id); |
|
82 |
- } elseif ($d === true) { |
|
83 |
- return 'true'; |
|
84 |
- } elseif ($d === false) { |
|
85 |
- return 'false'; |
|
86 |
- } elseif (is_array($d)) { |
|
87 |
- $res = []; |
|
88 |
- foreach ($d as $k => $v) { |
|
89 |
- $res[$k] = self::_encodeObjects($v); |
|
90 |
- } |
|
91 |
- return $res; |
|
92 |
- } else { |
|
93 |
- return Util\Util::utf8($d); |
|
94 |
- } |
|
95 |
- } |
|
96 |
- |
|
97 |
- /** |
|
98 |
- * @param string $method |
|
99 |
- * @param string $url |
|
100 |
- * @param array|null $params |
|
101 |
- * @param array|null $headers |
|
102 |
- * |
|
103 |
- * @return array An array whose first element is an API response and second |
|
104 |
- * element is the API key used to make the request. |
|
105 |
- * @throws Error\Api |
|
106 |
- * @throws Error\Authentication |
|
107 |
- * @throws Error\Card |
|
108 |
- * @throws Error\InvalidRequest |
|
109 |
- * @throws Error\Permission |
|
110 |
- * @throws Error\RateLimit |
|
111 |
- * @throws Error\Idempotency |
|
112 |
- * @throws Error\ApiConnection |
|
113 |
- */ |
|
114 |
- public function request($method, $url, $params = null, $headers = null) |
|
115 |
- { |
|
116 |
- $params = $params ?: []; |
|
117 |
- $headers = $headers ?: []; |
|
118 |
- list($rbody, $rcode, $rheaders, $myApiKey) = |
|
119 |
- $this->_requestRaw($method, $url, $params, $headers); |
|
120 |
- $json = $this->_interpretResponse($rbody, $rcode, $rheaders); |
|
121 |
- $resp = new ApiResponse($rbody, $rcode, $rheaders, $json); |
|
122 |
- return [$resp, $myApiKey]; |
|
123 |
- } |
|
124 |
- |
|
125 |
- /** |
|
126 |
- * @param string $rbody A JSON string. |
|
127 |
- * @param int $rcode |
|
128 |
- * @param array $rheaders |
|
129 |
- * @param array $resp |
|
130 |
- * |
|
131 |
- * @throws Error\InvalidRequest if the error is caused by the user. |
|
132 |
- * @throws Error\Authentication if the error is caused by a lack of |
|
133 |
- * permissions. |
|
134 |
- * @throws Error\Permission if the error is caused by insufficient |
|
135 |
- * permissions. |
|
136 |
- * @throws Error\Card if the error is the error code is 402 (payment |
|
137 |
- * required) |
|
138 |
- * @throws Error\InvalidRequest if the error is caused by the user. |
|
139 |
- * @throws Error\Idempotency if the error is caused by an idempotency key. |
|
140 |
- * @throws Error\Permission if the error is caused by insufficient |
|
141 |
- * permissions. |
|
142 |
- * @throws Error\RateLimit if the error is caused by too many requests |
|
143 |
- * hitting the API. |
|
144 |
- * @throws Error\Api otherwise. |
|
145 |
- */ |
|
146 |
- public function handleErrorResponse($rbody, $rcode, $rheaders, $resp) |
|
147 |
- { |
|
148 |
- if (!is_array($resp) || !isset($resp['error'])) { |
|
149 |
- $msg = "Invalid response object from API: $rbody " |
|
150 |
- . "(HTTP response code was $rcode)"; |
|
151 |
- throw new Error\Api($msg, $rcode, $rbody, $resp, $rheaders); |
|
152 |
- } |
|
153 |
- |
|
154 |
- $errorData = $resp['error']; |
|
155 |
- |
|
156 |
- #echo $rbody;exit; |
|
157 |
- |
|
158 |
- $error = null; |
|
159 |
- if (!$error) { |
|
160 |
- $error = self::_specificAPIError($rbody, $rcode, $rheaders, $resp, $errorData); |
|
161 |
- } |
|
162 |
- |
|
163 |
- throw $error; |
|
164 |
- } |
|
165 |
- |
|
166 |
- /** |
|
167 |
- * @static |
|
168 |
- * |
|
169 |
- * @param string $rbody |
|
170 |
- * @param int $rcode |
|
171 |
- * @param array $rheaders |
|
172 |
- * @param array $resp |
|
173 |
- * @param array $errorData |
|
174 |
- * |
|
175 |
- * @return Error\RateLimit|Error\Idempotency|Error\InvalidRequest|Error\Authentication|Error\Card|Error\Permission|Error\Api |
|
176 |
- */ |
|
177 |
- private static function _specificAPIError($rbody, $rcode, $rheaders, $resp, $errorData) |
|
178 |
- { |
|
179 |
- $msg = isset($errorData['message']) ? $errorData['message'] : null; |
|
180 |
- $param = isset($errorData['param']) ? $errorData['param'] : null; |
|
181 |
- $code = isset($errorData['code']) ? $errorData['code'] : null; |
|
182 |
- $type = isset($errorData['type']) ? $errorData['type'] : null; |
|
183 |
- |
|
184 |
- switch ($rcode) { |
|
185 |
- case 400: |
|
186 |
- // 'rate_limit' code is deprecated, but left here for backwards compatibility |
|
187 |
- // for API versions earlier than 2015-09-08 |
|
188 |
- if ($code == 'rate_limit') { |
|
189 |
- return new Error\RateLimit($msg, $param, $rcode, $rbody, $resp, $rheaders); |
|
190 |
- } |
|
191 |
- if ($type == 'idempotency_error') { |
|
192 |
- return new Error\Idempotency($msg, $rcode, $rbody, $resp, $rheaders); |
|
193 |
- } |
|
194 |
- |
|
195 |
- // intentional fall-through |
|
196 |
- // no break |
|
197 |
- case 404: |
|
198 |
- return new Error\InvalidRequest($msg, $param, $rcode, $rbody, $resp, $rheaders); |
|
199 |
- case 401: |
|
200 |
- return new Error\Authentication($msg, $rcode, $rbody, $resp, $rheaders); |
|
201 |
- case 402: |
|
202 |
- return new Error\Card($msg, $param, $code, $rcode, $rbody, $resp, $rheaders); |
|
203 |
- case 403: |
|
204 |
- return new Error\Permission($msg, $rcode, $rbody, $resp, $rheaders); |
|
205 |
- case 429: |
|
206 |
- return new Error\RateLimit($msg, $param, $rcode, $rbody, $resp, $rheaders); |
|
207 |
- default: |
|
208 |
- return new Error\Api($msg, $rcode, $rbody, $resp, $rheaders); |
|
209 |
- } |
|
210 |
- } |
|
211 |
- |
|
212 |
- /** |
|
213 |
- * @static |
|
214 |
- * |
|
215 |
- * @param null|array $appInfo |
|
216 |
- * |
|
217 |
- * @return null|string |
|
218 |
- */ |
|
219 |
- private static function _formatAppInfo($appInfo) |
|
220 |
- { |
|
221 |
- if ($appInfo !== null) { |
|
222 |
- $string = $appInfo['name']; |
|
223 |
- if ($appInfo['version'] !== null) { |
|
224 |
- $string .= '/' . $appInfo['version']; |
|
225 |
- } |
|
226 |
- if ($appInfo['url'] !== null) { |
|
227 |
- $string .= ' (' . $appInfo['url'] . ')'; |
|
228 |
- } |
|
229 |
- return $string; |
|
230 |
- } else { |
|
231 |
- return null; |
|
232 |
- } |
|
233 |
- } |
|
234 |
- |
|
235 |
- /** |
|
236 |
- * @static |
|
237 |
- * |
|
238 |
- * @param string $apiKey |
|
239 |
- * @param null $clientInfo |
|
240 |
- * |
|
241 |
- * @return array |
|
242 |
- */ |
|
243 |
- private static function _defaultHeaders($apiKey, $clientInfo = null) |
|
244 |
- { |
|
245 |
- $uaString = 'Telnyx/v2 PhpBindings/' . Telnyx::VERSION; |
|
246 |
- |
|
247 |
- $langVersion = phpversion(); |
|
248 |
- $uname = php_uname(); |
|
249 |
- |
|
250 |
- $appInfo = Telnyx::getAppInfo(); |
|
251 |
- $ua = [ |
|
252 |
- 'bindings_version' => Telnyx::VERSION, |
|
253 |
- 'lang' => 'php', |
|
254 |
- 'lang_version' => $langVersion, |
|
255 |
- 'publisher' => 'telnyx', |
|
256 |
- 'uname' => $uname, |
|
257 |
- ]; |
|
258 |
- if ($clientInfo) { |
|
259 |
- $ua = array_merge($clientInfo, $ua); |
|
260 |
- } |
|
261 |
- if ($appInfo !== null) { |
|
262 |
- $uaString .= ' ' . self::_formatAppInfo($appInfo); |
|
263 |
- $ua['application'] = $appInfo; |
|
264 |
- } |
|
265 |
- |
|
266 |
- $defaultHeaders = [ |
|
267 |
- 'X-Telnyx-Client-User-Agent' => json_encode($ua), |
|
268 |
- 'User-Agent' => $uaString, |
|
269 |
- 'Authorization' => 'Bearer ' . $apiKey, |
|
270 |
- ]; |
|
271 |
- return $defaultHeaders; |
|
272 |
- } |
|
273 |
- |
|
274 |
- /** |
|
275 |
- * @param string $method |
|
276 |
- * @param string $url |
|
277 |
- * @param array $params |
|
278 |
- * @param array $headers |
|
279 |
- * |
|
280 |
- * @return array |
|
281 |
- * @throws Error\Api |
|
282 |
- * @throws Error\ApiConnection |
|
283 |
- * @throws Error\Authentication |
|
284 |
- */ |
|
285 |
- private function _requestRaw($method, $url, $params, $headers) |
|
286 |
- { |
|
287 |
- $myApiKey = $this->_apiKey; |
|
288 |
- if (!$myApiKey) { |
|
289 |
- $myApiKey = Telnyx::$apiKey; |
|
290 |
- } |
|
291 |
- |
|
292 |
- if (!$myApiKey) { |
|
293 |
- $msg = 'No API key provided. (HINT: set your API key using ' |
|
294 |
- . '"Telnyx::setApiKey(<API-KEY>)". You can generate API keys from ' |
|
295 |
- . 'the Telnyx web interface. See https://developers.telnyx.com/docs/v2/development/authentication ' |
|
296 |
- . 'for details, or email support@telnyx.com if you have any questions.'; |
|
297 |
- throw new Error\Authentication($msg); |
|
298 |
- } |
|
299 |
- |
|
300 |
- // Clients can supply arbitrary additional keys to be included in the |
|
301 |
- // X-Telnyx-Client-User-Agent header via the optional getUserAgentInfo() |
|
302 |
- // method |
|
303 |
- $clientUAInfo = null; |
|
304 |
- if (method_exists($this->httpClient(), 'getUserAgentInfo')) { |
|
305 |
- $clientUAInfo = $this->httpClient()->getUserAgentInfo(); |
|
306 |
- } |
|
307 |
- |
|
308 |
- $absUrl = $this->_apiBase.$url; |
|
309 |
- $params = self::_encodeObjects($params); |
|
310 |
- $defaultHeaders = $this->_defaultHeaders($myApiKey, $clientUAInfo); |
|
311 |
- if (Telnyx::$apiVersion) { |
|
312 |
- $defaultHeaders['Telnyx-Version'] = Telnyx::$apiVersion; |
|
313 |
- } |
|
314 |
- |
|
315 |
- if (Telnyx::$accountId) { |
|
316 |
- $defaultHeaders['Telnyx-Account'] = Telnyx::$accountId; |
|
317 |
- } |
|
318 |
- |
|
319 |
- if (Telnyx::$enableTelemetry && self::$requestTelemetry != null) { |
|
320 |
- $defaultHeaders["X-Telnyx-Client-Telemetry"] = self::_telemetryJson(self::$requestTelemetry); |
|
321 |
- } |
|
322 |
- |
|
323 |
- $hasFile = false; |
|
324 |
- $hasCurlFile = class_exists('\CURLFile', false); |
|
325 |
- foreach ($params as $k => $v) { |
|
326 |
- if (is_resource($v)) { |
|
327 |
- $hasFile = true; |
|
328 |
- $params[$k] = self::_processResourceParam($v, $hasCurlFile); |
|
329 |
- } elseif ($hasCurlFile && $v instanceof \CURLFile) { |
|
330 |
- $hasFile = true; |
|
331 |
- } |
|
332 |
- } |
|
333 |
- |
|
334 |
- if ($hasFile) { |
|
335 |
- $defaultHeaders['Content-Type'] = 'multipart/form-data'; |
|
336 |
- } else { |
|
337 |
- $defaultHeaders['Content-Type'] = 'application/json'; |
|
338 |
- } |
|
339 |
- |
|
340 |
- $combinedHeaders = array_merge($defaultHeaders, $headers); |
|
341 |
- $rawHeaders = []; |
|
342 |
- |
|
343 |
- foreach ($combinedHeaders as $header => $value) { |
|
344 |
- $rawHeaders[] = $header . ': ' . $value; |
|
345 |
- } |
|
346 |
- |
|
347 |
- $requestStartMs = Util\Util::currentTimeMillis(); |
|
348 |
- |
|
349 |
- list($rbody, $rcode, $rheaders) = $this->httpClient()->request( |
|
350 |
- $method, |
|
351 |
- $absUrl, |
|
352 |
- $rawHeaders, |
|
353 |
- $params, |
|
354 |
- $hasFile |
|
355 |
- ); |
|
356 |
- |
|
357 |
-// if (array_key_exists('request-id', $rheaders)) { |
|
358 |
- if (property_exists($rheaders, 'request-id') && (null !== $rheaders->request-id)) { |
|
359 |
- self::$requestTelemetry = new RequestTelemetry( |
|
360 |
- $rheaders['request-id'], |
|
361 |
- Util\Util::currentTimeMillis() - $requestStartMs |
|
362 |
- ); |
|
363 |
- } |
|
364 |
- |
|
365 |
- return [$rbody, $rcode, $rheaders, $myApiKey]; |
|
366 |
- } |
|
367 |
- |
|
368 |
- /** |
|
369 |
- * @param resource $resource |
|
370 |
- * @param bool $hasCurlFile |
|
371 |
- * |
|
372 |
- * @return \CURLFile|string |
|
373 |
- * @throws Error\Api |
|
374 |
- */ |
|
375 |
- private function _processResourceParam($resource, $hasCurlFile) |
|
376 |
- { |
|
377 |
- if (get_resource_type($resource) !== 'stream') { |
|
378 |
- throw new Error\Api( |
|
379 |
- 'Attempted to upload a resource that is not a stream' |
|
380 |
- ); |
|
381 |
- } |
|
382 |
- |
|
383 |
- $metaData = stream_get_meta_data($resource); |
|
384 |
- if ($metaData['wrapper_type'] !== 'plainfile') { |
|
385 |
- throw new Error\Api( |
|
386 |
- 'Only plainfile resource streams are supported' |
|
387 |
- ); |
|
388 |
- } |
|
389 |
- |
|
390 |
- if ($hasCurlFile) { |
|
391 |
- // We don't have the filename or mimetype, but the API doesn't care |
|
392 |
- return new \CURLFile($metaData['uri']); |
|
393 |
- } else { |
|
394 |
- return '@'.$metaData['uri']; |
|
395 |
- } |
|
396 |
- } |
|
397 |
- |
|
398 |
- /** |
|
399 |
- * @param string $rbody |
|
400 |
- * @param int $rcode |
|
401 |
- * @param array $rheaders |
|
402 |
- * |
|
403 |
- * @return mixed |
|
404 |
- * @throws Error\Api |
|
405 |
- * @throws Error\Authentication |
|
406 |
- * @throws Error\Card |
|
407 |
- * @throws Error\InvalidRequest |
|
408 |
- * @throws Error\Permission |
|
409 |
- * @throws Error\RateLimit |
|
410 |
- * @throws Error\Idempotency |
|
411 |
- */ |
|
412 |
- private function _interpretResponse($rbody, $rcode, $rheaders) |
|
413 |
- { |
|
414 |
- $resp = json_decode($rbody, true); |
|
415 |
- |
|
416 |
- // Move [data] to the parent node |
|
417 |
- if (isset($resp['data'])) { |
|
418 |
- $resp = $resp['data']; |
|
419 |
- } |
|
420 |
- |
|
421 |
- $jsonError = json_last_error(); |
|
422 |
- if ($resp === null && $jsonError !== JSON_ERROR_NONE) { |
|
423 |
- $msg = "Invalid response body from API: $rbody " |
|
424 |
- . "(HTTP response code was $rcode, json_last_error() was $jsonError)"; |
|
425 |
- throw new Error\Api($msg, $rcode, $rbody); |
|
426 |
- } |
|
427 |
- |
|
428 |
- if ($rcode < 200 || $rcode >= 300) { |
|
429 |
- $this->handleErrorResponse($rbody, $rcode, $rheaders, $resp); |
|
430 |
- } |
|
431 |
- return $resp; |
|
432 |
- } |
|
433 |
- |
|
434 |
- /** |
|
435 |
- * @static |
|
436 |
- * |
|
437 |
- * @param HttpClient\ClientInterface $client |
|
438 |
- */ |
|
439 |
- public static function setHttpClient($client) |
|
440 |
- { |
|
441 |
- self::$_httpClient = $client; |
|
442 |
- } |
|
443 |
- |
|
444 |
- /** |
|
445 |
- * @static |
|
446 |
- * |
|
447 |
- * Resets any stateful telemetry data |
|
448 |
- */ |
|
449 |
- public static function resetTelemetry() |
|
450 |
- { |
|
451 |
- self::$requestTelemetry = null; |
|
452 |
- } |
|
453 |
- |
|
454 |
- /** |
|
455 |
- * @return HttpClient\ClientInterface |
|
456 |
- */ |
|
457 |
- private function httpClient() |
|
458 |
- { |
|
459 |
- if (!self::$_httpClient) { |
|
460 |
- self::$_httpClient = HttpClient\CurlClient::instance(); |
|
461 |
- } |
|
462 |
- return self::$_httpClient; |
|
463 |
- } |
|
464 |
-} |
1 | 1 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,464 @@ |
1 |
+<?php |
|
2 |
+ |
|
3 |
+namespace Telnyx; |
|
4 |
+ |
|
5 |
+/** |
|
6 |
+ * Class ApiRequestor |
|
7 |
+ * |
|
8 |
+ * @package Telnyx |
|
9 |
+ */ |
|
10 |
+class ApiRequestor |
|
11 |
+{ |
|
12 |
+ /** |
|
13 |
+ * @var string|null |
|
14 |
+ */ |
|
15 |
+ private $_apiKey; |
|
16 |
+ |
|
17 |
+ /** |
|
18 |
+ * @var string |
|
19 |
+ */ |
|
20 |
+ private $_apiBase; |
|
21 |
+ |
|
22 |
+ /** |
|
23 |
+ * @var HttpClient\ClientInterface |
|
24 |
+ */ |
|
25 |
+ private static $_httpClient; |
|
26 |
+ |
|
27 |
+ /** |
|
28 |
+ * @var RequestTelemetry |
|
29 |
+ */ |
|
30 |
+ private static $requestTelemetry; |
|
31 |
+ |
|
32 |
+ /** |
|
33 |
+ * ApiRequestor constructor. |
|
34 |
+ * |
|
35 |
+ * @param string|null $apiKey |
|
36 |
+ * @param string|null $apiBase |
|
37 |
+ */ |
|
38 |
+ public function __construct($apiKey = null, $apiBase = null) |
|
39 |
+ { |
|
40 |
+ $this->_apiKey = $apiKey; |
|
41 |
+ if (!$apiBase) { |
|
42 |
+ $apiBase = Telnyx::$apiBase; |
|
43 |
+ } |
|
44 |
+ $this->_apiBase = $apiBase; |
|
45 |
+ } |
|
46 |
+ |
|
47 |
+ /** |
|
48 |
+ * Creates a telemetry json blob for use in 'X-Telnyx-Client-Telemetry' headers |
|
49 |
+ * @static |
|
50 |
+ * |
|
51 |
+ * @param RequestTelemetry $requestTelemetry |
|
52 |
+ * @return string |
|
53 |
+ */ |
|
54 |
+ private static function _telemetryJson($requestTelemetry) |
|
55 |
+ { |
|
56 |
+ $payload = array( |
|
57 |
+ 'last_request_metrics' => array( |
|
58 |
+ 'request_id' => $requestTelemetry->requestId, |
|
59 |
+ 'request_duration_ms' => $requestTelemetry->requestDuration, |
|
60 |
+ )); |
|
61 |
+ |
|
62 |
+ $result = json_encode($payload); |
|
63 |
+ if ($result != false) { |
|
64 |
+ return $result; |
|
65 |
+ } else { |
|
66 |
+ Telnyx::getLogger()->error("Serializing telemetry payload failed!"); |
|
67 |
+ return "{}"; |
|
68 |
+ } |
|
69 |
+ } |
|
70 |
+ |
|
71 |
+ /** |
|
72 |
+ * @static |
|
73 |
+ * |
|
74 |
+ * @param ApiResource|bool|array|mixed $d |
|
75 |
+ * |
|
76 |
+ * @return ApiResource|array|string|mixed |
|
77 |
+ */ |
|
78 |
+ private static function _encodeObjects($d) |
|
79 |
+ { |
|
80 |
+ if ($d instanceof ApiResource) { |
|
81 |
+ return Util\Util::utf8($d->id); |
|
82 |
+ } elseif ($d === true) { |
|
83 |
+ return 'true'; |
|
84 |
+ } elseif ($d === false) { |
|
85 |
+ return 'false'; |
|
86 |
+ } elseif (is_array($d)) { |
|
87 |
+ $res = []; |
|
88 |
+ foreach ($d as $k => $v) { |
|
89 |
+ $res[$k] = self::_encodeObjects($v); |
|
90 |
+ } |
|
91 |
+ return $res; |
|
92 |
+ } else { |
|
93 |
+ return Util\Util::utf8($d); |
|
94 |
+ } |
|
95 |
+ } |
|
96 |
+ |
|
97 |
+ /** |
|
98 |
+ * @param string $method |
|
99 |
+ * @param string $url |
|
100 |
+ * @param array|null $params |
|
101 |
+ * @param array|null $headers |
|
102 |
+ * |
|
103 |
+ * @return array An array whose first element is an API response and second |
|
104 |
+ * element is the API key used to make the request. |
|
105 |
+ * @throws Error\Api |
|
106 |
+ * @throws Error\Authentication |
|
107 |
+ * @throws Error\Card |
|
108 |
+ * @throws Error\InvalidRequest |
|
109 |
+ * @throws Error\Permission |
|
110 |
+ * @throws Error\RateLimit |
|
111 |
+ * @throws Error\Idempotency |
|
112 |
+ * @throws Error\ApiConnection |
|
113 |
+ */ |
|
114 |
+ public function request($method, $url, $params = null, $headers = null) |
|
115 |
+ { |
|
116 |
+ $params = $params ?: []; |
|
117 |
+ $headers = $headers ?: []; |
|
118 |
+ list($rbody, $rcode, $rheaders, $myApiKey) = |
|
119 |
+ $this->_requestRaw($method, $url, $params, $headers); |
|
120 |
+ $json = $this->_interpretResponse($rbody, $rcode, $rheaders); |
|
121 |
+ $resp = new ApiResponse($rbody, $rcode, $rheaders, $json); |
|
122 |
+ return [$resp, $myApiKey]; |
|
123 |
+ } |
|
124 |
+ |
|
125 |
+ /** |
|
126 |
+ * @param string $rbody A JSON string. |
|
127 |
+ * @param int $rcode |
|
128 |
+ * @param array $rheaders |
|
129 |
+ * @param array $resp |
|
130 |
+ * |
|
131 |
+ * @throws Error\InvalidRequest if the error is caused by the user. |
|
132 |
+ * @throws Error\Authentication if the error is caused by a lack of |
|
133 |
+ * permissions. |
|
134 |
+ * @throws Error\Permission if the error is caused by insufficient |
|
135 |
+ * permissions. |
|
136 |
+ * @throws Error\Card if the error is the error code is 402 (payment |
|
137 |
+ * required) |
|
138 |
+ * @throws Error\InvalidRequest if the error is caused by the user. |
|
139 |
+ * @throws Error\Idempotency if the error is caused by an idempotency key. |
|
140 |
+ * @throws Error\Permission if the error is caused by insufficient |
|
141 |
+ * permissions. |
|
142 |
+ * @throws Error\RateLimit if the error is caused by too many requests |
|
143 |
+ * hitting the API. |
|
144 |
+ * @throws Error\Api otherwise. |
|
145 |
+ */ |
|
146 |
+ public function handleErrorResponse($rbody, $rcode, $rheaders, $resp) |
|
147 |
+ { |
|
148 |
+ if (!is_array($resp) || !isset($resp['error'])) { |
|
149 |
+ $msg = "Invalid response object from API: $rbody " |
|
150 |
+ . "(HTTP response code was $rcode)"; |
|
151 |
+ throw new Error\Api($msg, $rcode, $rbody, $resp, $rheaders); |
|
152 |
+ } |
|
153 |
+ |
|
154 |
+ $errorData = $resp['error']; |
|
155 |
+ |
|
156 |
+ #echo $rbody;exit; |
|
157 |
+ |
|
158 |
+ $error = null; |
|
159 |
+ if (!$error) { |
|
160 |
+ $error = self::_specificAPIError($rbody, $rcode, $rheaders, $resp, $errorData); |
|
161 |
+ } |
|
162 |
+ |
|
163 |
+ throw $error; |
|
164 |
+ } |
|
165 |
+ |
|
166 |
+ /** |
|
167 |
+ * @static |
|
168 |
+ * |
|
169 |
+ * @param string $rbody |
|
170 |
+ * @param int $rcode |
|
171 |
+ * @param array $rheaders |
|
172 |
+ * @param array $resp |
|
173 |
+ * @param array $errorData |
|
174 |
+ * |
|
175 |
+ * @return Error\RateLimit|Error\Idempotency|Error\InvalidRequest|Error\Authentication|Error\Card|Error\Permission|Error\Api |
|
176 |
+ */ |
|
177 |
+ private static function _specificAPIError($rbody, $rcode, $rheaders, $resp, $errorData) |
|
178 |
+ { |
|
179 |
+ $msg = isset($errorData['message']) ? $errorData['message'] : null; |
|
180 |
+ $param = isset($errorData['param']) ? $errorData['param'] : null; |
|
181 |
+ $code = isset($errorData['code']) ? $errorData['code'] : null; |
|
182 |
+ $type = isset($errorData['type']) ? $errorData['type'] : null; |
|
183 |
+ |
|
184 |
+ switch ($rcode) { |
|
185 |
+ case 400: |
|
186 |
+ // 'rate_limit' code is deprecated, but left here for backwards compatibility |
|
187 |
+ // for API versions earlier than 2015-09-08 |
|
188 |
+ if ($code == 'rate_limit') { |
|
189 |
+ return new Error\RateLimit($msg, $param, $rcode, $rbody, $resp, $rheaders); |
|
190 |
+ } |
|
191 |
+ if ($type == 'idempotency_error') { |
|
192 |
+ return new Error\Idempotency($msg, $rcode, $rbody, $resp, $rheaders); |
|
193 |
+ } |
|
194 |
+ |
|
195 |
+ // intentional fall-through |
|
196 |
+ // no break |
|
197 |
+ case 404: |
|
198 |
+ return new Error\InvalidRequest($msg, $param, $rcode, $rbody, $resp, $rheaders); |
|
199 |
+ case 401: |
|
200 |
+ return new Error\Authentication($msg, $rcode, $rbody, $resp, $rheaders); |
|
201 |
+ case 402: |
|
202 |
+ return new Error\Card($msg, $param, $code, $rcode, $rbody, $resp, $rheaders); |
|
203 |
+ case 403: |
|
204 |
+ return new Error\Permission($msg, $rcode, $rbody, $resp, $rheaders); |
|
205 |
+ case 429: |
|
206 |
+ return new Error\RateLimit($msg, $param, $rcode, $rbody, $resp, $rheaders); |
|
207 |
+ default: |
|
208 |
+ return new Error\Api($msg, $rcode, $rbody, $resp, $rheaders); |
|
209 |
+ } |
|
210 |
+ } |
|
211 |
+ |
|
212 |
+ /** |
|
213 |
+ * @static |
|
214 |
+ * |
|
215 |
+ * @param null|array $appInfo |
|
216 |
+ * |
|
217 |
+ * @return null|string |
|
218 |
+ */ |
|
219 |
+ private static function _formatAppInfo($appInfo) |
|
220 |
+ { |
|
221 |
+ if ($appInfo !== null) { |
|
222 |
+ $string = $appInfo['name']; |
|
223 |
+ if ($appInfo['version'] !== null) { |
|
224 |
+ $string .= '/' . $appInfo['version']; |
|
225 |
+ } |
|
226 |
+ if ($appInfo['url'] !== null) { |
|
227 |
+ $string .= ' (' . $appInfo['url'] . ')'; |
|
228 |
+ } |
|
229 |
+ return $string; |
|
230 |
+ } else { |
|
231 |
+ return null; |
|
232 |
+ } |
|
233 |
+ } |
|
234 |
+ |
|
235 |
+ /** |
|
236 |
+ * @static |
|
237 |
+ * |
|
238 |
+ * @param string $apiKey |
|
239 |
+ * @param null $clientInfo |
|
240 |
+ * |
|
241 |
+ * @return array |
|
242 |
+ */ |
|
243 |
+ private static function _defaultHeaders($apiKey, $clientInfo = null) |
|
244 |
+ { |
|
245 |
+ $uaString = 'Telnyx/v2 PhpBindings/' . Telnyx::VERSION; |
|
246 |
+ |
|
247 |
+ $langVersion = phpversion(); |
|
248 |
+ $uname = php_uname(); |
|
249 |
+ |
|
250 |
+ $appInfo = Telnyx::getAppInfo(); |
|
251 |
+ $ua = [ |
|
252 |
+ 'bindings_version' => Telnyx::VERSION, |
|
253 |
+ 'lang' => 'php', |
|
254 |
+ 'lang_version' => $langVersion, |
|
255 |
+ 'publisher' => 'telnyx', |
|
256 |
+ 'uname' => $uname, |
|
257 |
+ ]; |
|
258 |
+ if ($clientInfo) { |
|
259 |
+ $ua = array_merge($clientInfo, $ua); |
|
260 |
+ } |
|
261 |
+ if ($appInfo !== null) { |
|
262 |
+ $uaString .= ' ' . self::_formatAppInfo($appInfo); |
|
263 |
+ $ua['application'] = $appInfo; |
|
264 |
+ } |
|
265 |
+ |
|
266 |
+ $defaultHeaders = [ |
|
267 |
+ 'X-Telnyx-Client-User-Agent' => json_encode($ua), |
|
268 |
+ 'User-Agent' => $uaString, |
|
269 |
+ 'Authorization' => 'Bearer ' . $apiKey, |
|
270 |
+ ]; |
|
271 |
+ return $defaultHeaders; |
|
272 |
+ } |
|
273 |
+ |
|
274 |
+ /** |
|
275 |
+ * @param string $method |
|
276 |
+ * @param string $url |
|
277 |
+ * @param array $params |
|
278 |
+ * @param array $headers |
|
279 |
+ * |
|
280 |
+ * @return array |
|
281 |
+ * @throws Error\Api |
|
282 |
+ * @throws Error\ApiConnection |
|
283 |
+ * @throws Error\Authentication |
|
284 |
+ */ |
|
285 |
+ private function _requestRaw($method, $url, $params, $headers) |
|
286 |
+ { |
|
287 |
+ $myApiKey = $this->_apiKey; |
|
288 |
+ if (!$myApiKey) { |
|
289 |
+ $myApiKey = Telnyx::$apiKey; |
|
290 |
+ } |
|
291 |
+ |
|
292 |
+ if (!$myApiKey) { |
|
293 |
+ $msg = 'No API key provided. (HINT: set your API key using ' |
|
294 |
+ . '"Telnyx::setApiKey(<API-KEY>)". You can generate API keys from ' |
|
295 |
+ . 'the Telnyx web interface. See https://developers.telnyx.com/docs/v2/development/authentication ' |
|
296 |
+ . 'for details, or email support@telnyx.com if you have any questions.'; |
|
297 |
+ throw new Error\Authentication($msg); |
|
298 |
+ } |
|
299 |
+ |
|
300 |
+ // Clients can supply arbitrary additional keys to be included in the |
|
301 |
+ // X-Telnyx-Client-User-Agent header via the optional getUserAgentInfo() |
|
302 |
+ // method |
|
303 |
+ $clientUAInfo = null; |
|
304 |
+ if (method_exists($this->httpClient(), 'getUserAgentInfo')) { |
|
305 |
+ $clientUAInfo = $this->httpClient()->getUserAgentInfo(); |
|
306 |
+ } |
|
307 |
+ |
|
308 |
+ $absUrl = $this->_apiBase.$url; |
|
309 |
+ $params = self::_encodeObjects($params); |
|
310 |
+ $defaultHeaders = $this->_defaultHeaders($myApiKey, $clientUAInfo); |
|
311 |
+ if (Telnyx::$apiVersion) { |
|
312 |
+ $defaultHeaders['Telnyx-Version'] = Telnyx::$apiVersion; |
|
313 |
+ } |
|
314 |
+ |
|
315 |
+ if (Telnyx::$accountId) { |
|
316 |
+ $defaultHeaders['Telnyx-Account'] = Telnyx::$accountId; |
|
317 |
+ } |
|
318 |
+ |
|
319 |
+ if (Telnyx::$enableTelemetry && self::$requestTelemetry != null) { |
|
320 |
+ $defaultHeaders["X-Telnyx-Client-Telemetry"] = self::_telemetryJson(self::$requestTelemetry); |
|
321 |
+ } |
|
322 |
+ |
|
323 |
+ $hasFile = false; |
|
324 |
+ $hasCurlFile = class_exists('\CURLFile', false); |
|
325 |
+ foreach ($params as $k => $v) { |
|
326 |
+ if (is_resource($v)) { |
|
327 |
+ $hasFile = true; |
|
328 |
+ $params[$k] = self::_processResourceParam($v, $hasCurlFile); |
|
329 |
+ } elseif ($hasCurlFile && $v instanceof \CURLFile) { |
|
330 |
+ $hasFile = true; |
|
331 |
+ } |
|
332 |
+ } |
|
333 |
+ |
|
334 |
+ if ($hasFile) { |
|
335 |
+ $defaultHeaders['Content-Type'] = 'multipart/form-data'; |
|
336 |
+ } else { |
|
337 |
+ $defaultHeaders['Content-Type'] = 'application/json'; |
|
338 |
+ } |
|
339 |
+ |
|
340 |
+ $combinedHeaders = array_merge($defaultHeaders, $headers); |
|
341 |
+ $rawHeaders = []; |
|
342 |
+ |
|
343 |
+ foreach ($combinedHeaders as $header => $value) { |
|
344 |
+ $rawHeaders[] = $header . ': ' . $value; |
|
345 |
+ } |
|
346 |
+ |
|
347 |
+ $requestStartMs = Util\Util::currentTimeMillis(); |
|
348 |
+ |
|
349 |
+ list($rbody, $rcode, $rheaders) = $this->httpClient()->request( |
|
350 |
+ $method, |
|
351 |
+ $absUrl, |
|
352 |
+ $rawHeaders, |
|
353 |
+ $params, |
|
354 |
+ $hasFile |
|
355 |
+ ); |
|
356 |
+ |
|
357 |
+// if (array_key_exists('request-id', $rheaders)) { |
|
358 |
+ if (property_exists($rheaders, 'request-id') && (null !== $rheaders->request-id)) { |
|
359 |
+ self::$requestTelemetry = new RequestTelemetry( |
|
360 |
+ $rheaders['request-id'], |
|
361 |
+ Util\Util::currentTimeMillis() - $requestStartMs |
|
362 |
+ ); |
|
363 |
+ } |
|
364 |
+ |
|
365 |
+ return [$rbody, $rcode, $rheaders, $myApiKey]; |
|
366 |
+ } |
|
367 |
+ |
|
368 |
+ /** |
|
369 |
+ * @param resource $resource |
|
370 |
+ * @param bool $hasCurlFile |
|
371 |
+ * |
|
372 |
+ * @return \CURLFile|string |
|
373 |
+ * @throws Error\Api |
|
374 |
+ */ |
|
375 |
+ private function _processResourceParam($resource, $hasCurlFile) |
|
376 |
+ { |
|
377 |
+ if (get_resource_type($resource) !== 'stream') { |
|
378 |
+ throw new Error\Api( |
|
379 |
+ 'Attempted to upload a resource that is not a stream' |
|
380 |
+ ); |
|
381 |
+ } |
|
382 |
+ |
|
383 |
+ $metaData = stream_get_meta_data($resource); |
|
384 |
+ if ($metaData['wrapper_type'] !== 'plainfile') { |
|
385 |
+ throw new Error\Api( |
|
386 |
+ 'Only plainfile resource streams are supported' |
|
387 |
+ ); |
|
388 |
+ } |
|
389 |
+ |
|
390 |
+ if ($hasCurlFile) { |
|
391 |
+ // We don't have the filename or mimetype, but the API doesn't care |
|
392 |
+ return new \CURLFile($metaData['uri']); |
|
393 |
+ } else { |
|
394 |
+ return '@'.$metaData['uri']; |
|
395 |
+ } |
|
396 |
+ } |
|
397 |
+ |
|
398 |
+ /** |
|
399 |
+ * @param string $rbody |
|
400 |
+ * @param int $rcode |
|
401 |
+ * @param array $rheaders |
|
402 |
+ * |
|
403 |
+ * @return mixed |
|
404 |
+ * @throws Error\Api |
|
405 |
+ * @throws Error\Authentication |
|
406 |
+ * @throws Error\Card |
|
407 |
+ * @throws Error\InvalidRequest |
|
408 |
+ * @throws Error\Permission |
|
409 |
+ * @throws Error\RateLimit |
|
410 |
+ * @throws Error\Idempotency |
|
411 |
+ */ |
|
412 |
+ private function _interpretResponse($rbody, $rcode, $rheaders) |
|
413 |
+ { |
|
414 |
+ $resp = json_decode($rbody, true); |
|
415 |
+ |
|
416 |
+ // Move [data] to the parent node |
|
417 |
+ if (isset($resp['data'])) { |
|
418 |
+ $resp = $resp['data']; |
|
419 |
+ } |
|
420 |
+ |
|
421 |
+ $jsonError = json_last_error(); |
|
422 |
+ if ($resp === null && $jsonError !== JSON_ERROR_NONE) { |
|
423 |
+ $msg = "Invalid response body from API: $rbody " |
|
424 |
+ . "(HTTP response code was $rcode, json_last_error() was $jsonError)"; |
|
425 |
+ throw new Error\Api($msg, $rcode, $rbody); |
|
426 |
+ } |
|
427 |
+ |
|
428 |
+ if ($rcode < 200 || $rcode >= 300) { |
|
429 |
+ $this->handleErrorResponse($rbody, $rcode, $rheaders, $resp); |
|
430 |
+ } |
|
431 |
+ return $resp; |
|
432 |
+ } |
|
433 |
+ |
|
434 |
+ /** |
|
435 |
+ * @static |
|
436 |
+ * |
|
437 |
+ * @param HttpClient\ClientInterface $client |
|
438 |
+ */ |
|
439 |
+ public static function setHttpClient($client) |
|
440 |
+ { |
|
441 |
+ self::$_httpClient = $client; |
|
442 |
+ } |
|
443 |
+ |
|
444 |
+ /** |
|
445 |
+ * @static |
|
446 |
+ * |
|
447 |
+ * Resets any stateful telemetry data |
|
448 |
+ */ |
|
449 |
+ public static function resetTelemetry() |
|
450 |
+ { |
|
451 |
+ self::$requestTelemetry = null; |
|
452 |
+ } |
|
453 |
+ |
|
454 |
+ /** |
|
455 |
+ * @return HttpClient\ClientInterface |
|
456 |
+ */ |
|
457 |
+ private function httpClient() |
|
458 |
+ { |
|
459 |
+ if (!self::$_httpClient) { |
|
460 |
+ self::$_httpClient = HttpClient\CurlClient::instance(); |
|
461 |
+ } |
|
462 |
+ return self::$_httpClient; |
|
463 |
+ } |
|
464 |
+} |
1 | 1 |
deleted file mode 100644 |
... | ... |
@@ -1,463 +0,0 @@ |
1 |
-<?php |
|
2 |
- |
|
3 |
-namespace Telnyx; |
|
4 |
- |
|
5 |
-/** |
|
6 |
- * Class ApiRequestor |
|
7 |
- * |
|
8 |
- * @package Telnyx |
|
9 |
- */ |
|
10 |
-class ApiRequestor |
|
11 |
-{ |
|
12 |
- /** |
|
13 |
- * @var string|null |
|
14 |
- */ |
|
15 |
- private $_apiKey; |
|
16 |
- |
|
17 |
- /** |
|
18 |
- * @var string |
|
19 |
- */ |
|
20 |
- private $_apiBase; |
|
21 |
- |
|
22 |
- /** |
|
23 |
- * @var HttpClient\ClientInterface |
|
24 |
- */ |
|
25 |
- private static $_httpClient; |
|
26 |
- |
|
27 |
- /** |
|
28 |
- * @var RequestTelemetry |
|
29 |
- */ |
|
30 |
- private static $requestTelemetry; |
|
31 |
- |
|
32 |
- /** |
|
33 |
- * ApiRequestor constructor. |
|
34 |
- * |
|
35 |
- * @param string|null $apiKey |
|
36 |
- * @param string|null $apiBase |
|
37 |
- */ |
|
38 |
- public function __construct($apiKey = null, $apiBase = null) |
|
39 |
- { |
|
40 |
- $this->_apiKey = $apiKey; |
|
41 |
- if (!$apiBase) { |
|
42 |
- $apiBase = Telnyx::$apiBase; |
|
43 |
- } |
|
44 |
- $this->_apiBase = $apiBase; |
|
45 |
- } |
|
46 |
- |
|
47 |
- /** |
|
48 |
- * Creates a telemetry json blob for use in 'X-Telnyx-Client-Telemetry' headers |
|
49 |
- * @static |
|
50 |
- * |
|
51 |
- * @param RequestTelemetry $requestTelemetry |
|
52 |
- * @return string |
|
53 |
- */ |
|
54 |
- private static function _telemetryJson($requestTelemetry) |
|
55 |
- { |
|
56 |
- $payload = array( |
|
57 |
- 'last_request_metrics' => array( |
|
58 |
- 'request_id' => $requestTelemetry->requestId, |
|
59 |
- 'request_duration_ms' => $requestTelemetry->requestDuration, |
|
60 |
- )); |
|
61 |
- |
|
62 |
- $result = json_encode($payload); |
|
63 |
- if ($result != false) { |
|
64 |
- return $result; |
|
65 |
- } else { |
|
66 |
- Telnyx::getLogger()->error("Serializing telemetry payload failed!"); |
|
67 |
- return "{}"; |
|
68 |
- } |
|
69 |
- } |
|
70 |
- |
|
71 |
- /** |
|
72 |
- * @static |
|
73 |
- * |
|
74 |
- * @param ApiResource|bool|array|mixed $d |
|
75 |
- * |
|
76 |
- * @return ApiResource|array|string|mixed |
|
77 |
- */ |
|
78 |
- private static function _encodeObjects($d) |
|
79 |
- { |
|
80 |
- if ($d instanceof ApiResource) { |
|
81 |
- return Util\Util::utf8($d->id); |
|
82 |
- } elseif ($d === true) { |
|
83 |
- return 'true'; |
|
84 |
- } elseif ($d === false) { |
|
85 |
- return 'false'; |
|
86 |
- } elseif (is_array($d)) { |
|
87 |
- $res = []; |
|
88 |
- foreach ($d as $k => $v) { |
|
89 |
- $res[$k] = self::_encodeObjects($v); |
|
90 |
- } |
|
91 |
- return $res; |
|
92 |
- } else { |
|
93 |
- return Util\Util::utf8($d); |
|
94 |
- } |
|
95 |
- } |
|
96 |
- |
|
97 |
- /** |
|
98 |
- * @param string $method |
|
99 |
- * @param string $url |
|
100 |
- * @param array|null $params |
|
101 |
- * @param array|null $headers |
|
102 |
- * |
|
103 |
- * @return array An array whose first element is an API response and second |
|
104 |
- * element is the API key used to make the request. |
|
105 |
- * @throws Error\Api |
|
106 |
- * @throws Error\Authentication |
|
107 |
- * @throws Error\Card |
|
108 |
- * @throws Error\InvalidRequest |
|
109 |
- * @throws Error\Permission |
|
110 |
- * @throws Error\RateLimit |
|
111 |
- * @throws Error\Idempotency |
|
112 |
- * @throws Error\ApiConnection |
|
113 |
- */ |
|
114 |
- public function request($method, $url, $params = null, $headers = null) |
|
115 |
- { |
|
116 |
- $params = $params ?: []; |
|
117 |
- $headers = $headers ?: []; |
|
118 |
- list($rbody, $rcode, $rheaders, $myApiKey) = |
|
119 |
- $this->_requestRaw($method, $url, $params, $headers); |
|
120 |
- $json = $this->_interpretResponse($rbody, $rcode, $rheaders); |
|
121 |
- $resp = new ApiResponse($rbody, $rcode, $rheaders, $json); |
|
122 |
- return [$resp, $myApiKey]; |
|
123 |
- } |
|
124 |
- |
|
125 |
- /** |
|
126 |
- * @param string $rbody A JSON string. |
|
127 |
- * @param int $rcode |
|
128 |
- * @param array $rheaders |
|
129 |
- * @param array $resp |
|
130 |
- * |
|
131 |
- * @throws Error\InvalidRequest if the error is caused by the user. |
|
132 |
- * @throws Error\Authentication if the error is caused by a lack of |
|
133 |
- * permissions. |
|
134 |
- * @throws Error\Permission if the error is caused by insufficient |
|
135 |
- * permissions. |
|
136 |
- * @throws Error\Card if the error is the error code is 402 (payment |
|
137 |
- * required) |
|
138 |
- * @throws Error\InvalidRequest if the error is caused by the user. |
|
139 |
- * @throws Error\Idempotency if the error is caused by an idempotency key. |
|
140 |
- * @throws Error\Permission if the error is caused by insufficient |
|
141 |
- * permissions. |
|
142 |
- * @throws Error\RateLimit if the error is caused by too many requests |
|
143 |
- * hitting the API. |
|
144 |
- * @throws Error\Api otherwise. |
|
145 |
- */ |
|
146 |
- public function handleErrorResponse($rbody, $rcode, $rheaders, $resp) |
|
147 |
- { |
|
148 |
- if (!is_array($resp) || !isset($resp['error'])) { |
|
149 |
- $msg = "Invalid response object from API: $rbody " |
|
150 |
- . "(HTTP response code was $rcode)"; |
|
151 |
- throw new Error\Api($msg, $rcode, $rbody, $resp, $rheaders); |
|
152 |
- } |
|
153 |
- |
|
154 |
- $errorData = $resp['error']; |
|
155 |
- |
|
156 |
- #echo $rbody;exit; |
|
157 |
- |
|
158 |
- $error = null; |
|
159 |
- if (!$error) { |
|
160 |
- $error = self::_specificAPIError($rbody, $rcode, $rheaders, $resp, $errorData); |
|
161 |
- } |
|
162 |
- |
|
163 |
- throw $error; |
|
164 |
- } |
|
165 |
- |
|
166 |
- /** |
|
167 |
- * @static |
|
168 |
- * |
|
169 |
- * @param string $rbody |
|
170 |
- * @param int $rcode |
|
171 |
- * @param array $rheaders |
|
172 |
- * @param array $resp |
|
173 |
- * @param array $errorData |
|
174 |
- * |
|
175 |
- * @return Error\RateLimit|Error\Idempotency|Error\InvalidRequest|Error\Authentication|Error\Card|Error\Permission|Error\Api |
|
176 |
- */ |
|
177 |
- private static function _specificAPIError($rbody, $rcode, $rheaders, $resp, $errorData) |
|
178 |
- { |
|
179 |
- $msg = isset($errorData['message']) ? $errorData['message'] : null; |
|
180 |
- $param = isset($errorData['param']) ? $errorData['param'] : null; |
|
181 |
- $code = isset($errorData['code']) ? $errorData['code'] : null; |
|
182 |
- $type = isset($errorData['type']) ? $errorData['type'] : null; |
|
183 |
- |
|
184 |
- switch ($rcode) { |
|
185 |
- case 400: |
|
186 |
- // 'rate_limit' code is deprecated, but left here for backwards compatibility |
|
187 |
- // for API versions earlier than 2015-09-08 |
|
188 |
- if ($code == 'rate_limit') { |
|
189 |
- return new Error\RateLimit($msg, $param, $rcode, $rbody, $resp, $rheaders); |
|
190 |
- } |
|
191 |
- if ($type == 'idempotency_error') { |
|
192 |
- return new Error\Idempotency($msg, $rcode, $rbody, $resp, $rheaders); |
|
193 |
- } |
|
194 |
- |
|
195 |
- // intentional fall-through |
|
196 |
- // no break |
|
197 |
- case 404: |
|
198 |
- return new Error\InvalidRequest($msg, $param, $rcode, $rbody, $resp, $rheaders); |
|
199 |
- case 401: |
|
200 |
- return new Error\Authentication($msg, $rcode, $rbody, $resp, $rheaders); |
|
201 |
- case 402: |
|
202 |
- return new Error\Card($msg, $param, $code, $rcode, $rbody, $resp, $rheaders); |
|
203 |
- case 403: |
|
204 |
- return new Error\Permission($msg, $rcode, $rbody, $resp, $rheaders); |
|
205 |
- case 429: |
|
206 |
- return new Error\RateLimit($msg, $param, $rcode, $rbody, $resp, $rheaders); |
|
207 |
- default: |
|
208 |
- return new Error\Api($msg, $rcode, $rbody, $resp, $rheaders); |
|
209 |
- } |
|
210 |
- } |
|
211 |
- |
|
212 |
- /** |
|
213 |
- * @static |
|
214 |
- * |
|
215 |
- * @param null|array $appInfo |
|
216 |
- * |
|
217 |
- * @return null|string |
|
218 |
- */ |
|
219 |
- private static function _formatAppInfo($appInfo) |
|
220 |
- { |
|
221 |
- if ($appInfo !== null) { |
|
222 |
- $string = $appInfo['name']; |
|
223 |
- if ($appInfo['version'] !== null) { |
|
224 |
- $string .= '/' . $appInfo['version']; |
|
225 |
- } |
|
226 |
- if ($appInfo['url'] !== null) { |
|
227 |
- $string .= ' (' . $appInfo['url'] . ')'; |
|
228 |
- } |
|
229 |
- return $string; |
|
230 |
- } else { |
|
231 |
- return null; |
|
232 |
- } |
|
233 |
- } |
|
234 |
- |
|
235 |
- /** |
|
236 |
- * @static |
|
237 |
- * |
|
238 |
- * @param string $apiKey |
|
239 |
- * @param null $clientInfo |
|
240 |
- * |
|
241 |
- * @return array |
|
242 |
- */ |
|
243 |
- private static function _defaultHeaders($apiKey, $clientInfo = null) |
|
244 |
- { |
|
245 |
- $uaString = 'Telnyx/v2 PhpBindings/' . Telnyx::VERSION; |
|
246 |
- |
|
247 |
- $langVersion = phpversion(); |
|
248 |
- $uname = php_uname(); |
|
249 |
- |
|
250 |
- $appInfo = Telnyx::getAppInfo(); |
|
251 |
- $ua = [ |
|
252 |
- 'bindings_version' => Telnyx::VERSION, |
|
253 |
- 'lang' => 'php', |
|
254 |
- 'lang_version' => $langVersion, |
|
255 |
- 'publisher' => 'telnyx', |
|
256 |
- 'uname' => $uname, |
|
257 |
- ]; |
|
258 |
- if ($clientInfo) { |
|
259 |
- $ua = array_merge($clientInfo, $ua); |
|
260 |
- } |
|
261 |
- if ($appInfo !== null) { |
|
262 |
- $uaString .= ' ' . self::_formatAppInfo($appInfo); |
|
263 |
- $ua['application'] = $appInfo; |
|
264 |
- } |
|
265 |
- |
|
266 |
- $defaultHeaders = [ |
|
267 |
- 'X-Telnyx-Client-User-Agent' => json_encode($ua), |
|
268 |
- 'User-Agent' => $uaString, |
|
269 |
- 'Authorization' => 'Bearer ' . $apiKey, |
|
270 |
- ]; |
|
271 |
- return $defaultHeaders; |
|
272 |
- } |
|
273 |
- |
|
274 |
- /** |
|
275 |
- * @param string $method |
|
276 |
- * @param string $url |
|
277 |
- * @param array $params |
|
278 |
- * @param array $headers |
|
279 |
- * |
|
280 |
- * @return array |
|
281 |
- * @throws Error\Api |
|
282 |
- * @throws Error\ApiConnection |
|
283 |
- * @throws Error\Authentication |
|
284 |
- */ |
|
285 |
- private function _requestRaw($method, $url, $params, $headers) |
|
286 |
- { |
|
287 |
- $myApiKey = $this->_apiKey; |
|
288 |
- if (!$myApiKey) { |
|
289 |
- $myApiKey = Telnyx::$apiKey; |
|
290 |
- } |
|
291 |
- |
|
292 |
- if (!$myApiKey) { |
|
293 |
- $msg = 'No API key provided. (HINT: set your API key using ' |
|
294 |
- . '"Telnyx::setApiKey(<API-KEY>)". You can generate API keys from ' |
|
295 |
- . 'the Telnyx web interface. See https://developers.telnyx.com/docs/v2/development/authentication ' |
|
296 |
- . 'for details, or email support@telnyx.com if you have any questions.'; |
|
297 |
- throw new Error\Authentication($msg); |
|
298 |
- } |
|
299 |
- |
|
300 |
- // Clients can supply arbitrary additional keys to be included in the |
|
301 |
- // X-Telnyx-Client-User-Agent header via the optional getUserAgentInfo() |
|
302 |
- // method |
|
303 |
- $clientUAInfo = null; |
|
304 |
- if (method_exists($this->httpClient(), 'getUserAgentInfo')) { |
|
305 |
- $clientUAInfo = $this->httpClient()->getUserAgentInfo(); |
|
306 |
- } |
|
307 |
- |
|
308 |
- $absUrl = $this->_apiBase.$url; |
|
309 |
- $params = self::_encodeObjects($params); |
|
310 |
- $defaultHeaders = $this->_defaultHeaders($myApiKey, $clientUAInfo); |
|
311 |
- if (Telnyx::$apiVersion) { |
|
312 |
- $defaultHeaders['Telnyx-Version'] = Telnyx::$apiVersion; |
|
313 |
- } |
|
314 |
- |
|
315 |
- if (Telnyx::$accountId) { |
|
316 |
- $defaultHeaders['Telnyx-Account'] = Telnyx::$accountId; |
|
317 |
- } |
|
318 |
- |
|
319 |
- if (Telnyx::$enableTelemetry && self::$requestTelemetry != null) { |
|
320 |
- $defaultHeaders["X-Telnyx-Client-Telemetry"] = self::_telemetryJson(self::$requestTelemetry); |
|
321 |
- } |
|
322 |
- |
|
323 |
- $hasFile = false; |
|
324 |
- $hasCurlFile = class_exists('\CURLFile', false); |
|
325 |
- foreach ($params as $k => $v) { |
|
326 |
- if (is_resource($v)) { |
|
327 |
- $hasFile = true; |
|
328 |
- $params[$k] = self::_processResourceParam($v, $hasCurlFile); |
|
329 |
- } elseif ($hasCurlFile && $v instanceof \CURLFile) { |
|
330 |
- $hasFile = true; |
|
331 |
- } |
|
332 |
- } |
|
333 |
- |
|
334 |
- if ($hasFile) { |
|
335 |
- $defaultHeaders['Content-Type'] = 'multipart/form-data'; |
|
336 |
- } else { |
|
337 |
- $defaultHeaders['Content-Type'] = 'application/json'; |
|
338 |
- } |
|
339 |
- |
|
340 |
- $combinedHeaders = array_merge($defaultHeaders, $headers); |
|
341 |
- $rawHeaders = []; |
|
342 |
- |
|
343 |
- foreach ($combinedHeaders as $header => $value) { |
|
344 |
- $rawHeaders[] = $header . ': ' . $value; |
|
345 |
- } |
|
346 |
- |
|
347 |
- $requestStartMs = Util\Util::currentTimeMillis(); |
|
348 |
- |
|
349 |
- list($rbody, $rcode, $rheaders) = $this->httpClient()->request( |
|
350 |
- $method, |
|
351 |
- $absUrl, |
|
352 |
- $rawHeaders, |
|
353 |
- $params, |
|
354 |
- $hasFile |
|
355 |
- ); |
|
356 |
- |
|
357 |
- if (array_key_exists('request-id', $rheaders)) { |
|
358 |
- self::$requestTelemetry = new RequestTelemetry( |
|
359 |
- $rheaders['request-id'], |
|
360 |
- Util\Util::currentTimeMillis() - $requestStartMs |
|
361 |
- ); |
|
362 |
- } |
|
363 |
- |
|
364 |
- return [$rbody, $rcode, $rheaders, $myApiKey]; |
|
365 |
- } |
|
366 |
- |
|
367 |
- /** |
|
368 |
- * @param resource $resource |
|
369 |
- * @param bool $hasCurlFile |
|
370 |
- * |
|
371 |
- * @return \CURLFile|string |
|
372 |
- * @throws Error\Api |
|
373 |
- */ |
|
374 |
- private function _processResourceParam($resource, $hasCurlFile) |
|
375 |
- { |
|
376 |
- if (get_resource_type($resource) !== 'stream') { |
|
377 |
- throw new Error\Api( |
|
378 |
- 'Attempted to upload a resource that is not a stream' |
|
379 |
- ); |
|
380 |
- } |
|
381 |
- |
|
382 |
- $metaData = stream_get_meta_data($resource); |
|
383 |
- if ($metaData['wrapper_type'] !== 'plainfile') { |
|
384 |
- throw new Error\Api( |
|
385 |
- 'Only plainfile resource streams are supported' |
|
386 |
- ); |
|
387 |
- } |
|
388 |
- |
|
389 |
- if ($hasCurlFile) { |
|
390 |
- // We don't have the filename or mimetype, but the API doesn't care |
|
391 |
- return new \CURLFile($metaData['uri']); |
|
392 |
- } else { |
|
393 |
- return '@'.$metaData['uri']; |
|
394 |
- } |
|
395 |
- } |
|
396 |
- |
|
397 |
- /** |
|
398 |
- * @param string $rbody |
|
399 |
- * @param int $rcode |
|
400 |
- * @param array $rheaders |
|
401 |
- * |
|
402 |
- * @return mixed |
|
403 |
- * @throws Error\Api |
|
404 |
- * @throws Error\Authentication |
|
405 |
- * @throws Error\Card |
|
406 |
- * @throws Error\InvalidRequest |
|
407 |
- * @throws Error\Permission |
|
408 |
- * @throws Error\RateLimit |
|
409 |
- * @throws Error\Idempotency |
|
410 |
- */ |
|
411 |
- private function _interpretResponse($rbody, $rcode, $rheaders) |
|
412 |
- { |
|
413 |
- $resp = json_decode($rbody, true); |
|
414 |
- |
|
415 |
- // Move [data] to the parent node |
|
416 |
- if (isset($resp['data'])) { |
|
417 |
- $resp = $resp['data']; |
|
418 |
- } |
|
419 |
- |
|
420 |
- $jsonError = json_last_error(); |
|
421 |
- if ($resp === null && $jsonError !== JSON_ERROR_NONE) { |
|
422 |
- $msg = "Invalid response body from API: $rbody " |
|
423 |
- . "(HTTP response code was $rcode, json_last_error() was $jsonError)"; |
|
424 |
- throw new Error\Api($msg, $rcode, $rbody); |
|
425 |
- } |
|
426 |
- |
|
427 |
- if ($rcode < 200 || $rcode >= 300) { |
|
428 |
- $this->handleErrorResponse($rbody, $rcode, $rheaders, $resp); |
|
429 |
- } |
|
430 |
- return $resp; |
|
431 |
- } |
|
432 |
- |
|
433 |
- /** |
|
434 |
- * @static |
|
435 |
- * |
|
436 |
- * @param HttpClient\ClientInterface $client |
|
437 |
- */ |
|
438 |
- public static function setHttpClient($client) |
|
439 |
- { |
|
440 |
- self::$_httpClient = $client; |
|
441 |
- } |
|
442 |
- |
|
443 |
- /** |
|
444 |
- * @static |
|
445 |
- * |
|
446 |
- * Resets any stateful telemetry data |
|
447 |
- */ |
|
448 |
- public static function resetTelemetry() |
|
449 |
- { |
|
450 |
- self::$requestTelemetry = null; |
|
451 |
- } |
|
452 |
- |
|
453 |
- /** |
|
454 |
- * @return HttpClient\ClientInterface |
|
455 |
- */ |
|
456 |
- private function httpClient() |
|
457 |
- { |
|
458 |
- if (!self::$_httpClient) { |
|
459 |
- self::$_httpClient = HttpClient\CurlClient::instance(); |
|
460 |
- } |
|
461 |
- return self::$_httpClient; |
|
462 |
- } |
|
463 |
-} |
1 | 1 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,463 @@ |
1 |
+<?php |
|
2 |
+ |
|
3 |
+namespace Telnyx; |
|
4 |
+ |
|
5 |
+/** |
|
6 |
+ * Class ApiRequestor |
|
7 |
+ * |
|
8 |
+ * @package Telnyx |
|
9 |
+ */ |
|
10 |
+class ApiRequestor |
|
11 |
+{ |
|
12 |
+ /** |
|
13 |
+ * @var string|null |
|
14 |
+ */ |
|
15 |
+ private $_apiKey; |
|
16 |
+ |
|
17 |
+ /** |
|
18 |
+ * @var string |
|
19 |
+ */ |
|
20 |
+ private $_apiBase; |
|
21 |
+ |
|
22 |
+ /** |
|
23 |
+ * @var HttpClient\ClientInterface |
|
24 |
+ */ |
|
25 |
+ private static $_httpClient; |
|
26 |
+ |
|
27 |
+ /** |
|
28 |
+ * @var RequestTelemetry |
|
29 |
+ */ |
|
30 |
+ private static $requestTelemetry; |
|
31 |
+ |
|
32 |
+ /** |
|
33 |
+ * ApiRequestor constructor. |
|
34 |
+ * |
|
35 |
+ * @param string|null $apiKey |
|
36 |
+ * @param string|null $apiBase |
|
37 |
+ */ |
|
38 |
+ public function __construct($apiKey = null, $apiBase = null) |
|
39 |
+ { |
|
40 |
+ $this->_apiKey = $apiKey; |
|
41 |
+ if (!$apiBase) { |
|
42 |
+ $apiBase = Telnyx::$apiBase; |
|
43 |
+ } |
|
44 |
+ $this->_apiBase = $apiBase; |
|
45 |
+ } |
|
46 |
+ |
|
47 |
+ /** |
|
48 |
+ * Creates a telemetry json blob for use in 'X-Telnyx-Client-Telemetry' headers |
|
49 |
+ * @static |
|
50 |
+ * |
|
51 |
+ * @param RequestTelemetry $requestTelemetry |
|
52 |
+ * @return string |
|
53 |
+ */ |
|
54 |
+ private static function _telemetryJson($requestTelemetry) |
|
55 |
+ { |
|
56 |
+ $payload = array( |
|
57 |
+ 'last_request_metrics' => array( |
|
58 |
+ 'request_id' => $requestTelemetry->requestId, |
|
59 |
+ 'request_duration_ms' => $requestTelemetry->requestDuration, |
|
60 |
+ )); |
|
61 |
+ |
|
62 |
+ $result = json_encode($payload); |
|
63 |
+ if ($result != false) { |
|
64 |
+ return $result; |
|
65 |
+ } else { |
|
66 |
+ Telnyx::getLogger()->error("Serializing telemetry payload failed!"); |
|
67 |
+ return "{}"; |
|
68 |
+ } |
|
69 |
+ } |
|
70 |
+ |
|
71 |
+ /** |
|
72 |
+ * @static |
|
73 |
+ * |
|
74 |
+ * @param ApiResource|bool|array|mixed $d |
|
75 |
+ * |
|
76 |
+ * @return ApiResource|array|string|mixed |
|
77 |
+ */ |
|
78 |
+ private static function _encodeObjects($d) |
|
79 |
+ { |
|
80 |
+ if ($d instanceof ApiResource) { |
|
81 |
+ return Util\Util::utf8($d->id); |
|
82 |
+ } elseif ($d === true) { |
|
83 |
+ return 'true'; |
|
84 |
+ } elseif ($d === false) { |
|
85 |
+ return 'false'; |
|
86 |
+ } elseif (is_array($d)) { |
|
87 |
+ $res = []; |
|
88 |
+ foreach ($d as $k => $v) { |
|
89 |
+ $res[$k] = self::_encodeObjects($v); |
|
90 |
+ } |
|
91 |
+ return $res; |
|
92 |
+ } else { |
|
93 |
+ return Util\Util::utf8($d); |
|
94 |
+ } |
|
95 |
+ } |
|
96 |
+ |
|
97 |
+ /** |
|
98 |
+ * @param string $method |
|
99 |
+ * @param string $url |
|
100 |
+ * @param array|null $params |
|
101 |
+ * @param array|null $headers |
|
102 |
+ * |
|
103 |
+ * @return array An array whose first element is an API response and second |
|
104 |
+ * element is the API key used to make the request. |
|
105 |
+ * @throws Error\Api |
|
106 |
+ * @throws Error\Authentication |
|
107 |
+ * @throws Error\Card |
|
108 |
+ * @throws Error\InvalidRequest |
|
109 |
+ * @throws Error\Permission |
|
110 |
+ * @throws Error\RateLimit |
|
111 |
+ * @throws Error\Idempotency |
|
112 |
+ * @throws Error\ApiConnection |
|
113 |
+ */ |
|
114 |
+ public function request($method, $url, $params = null, $headers = null) |
|
115 |
+ { |
|
116 |
+ $params = $params ?: []; |
|
117 |
+ $headers = $headers ?: []; |
|
118 |
+ list($rbody, $rcode, $rheaders, $myApiKey) = |
|
119 |
+ $this->_requestRaw($method, $url, $params, $headers); |
|
120 |
+ $json = $this->_interpretResponse($rbody, $rcode, $rheaders); |
|
121 |
+ $resp = new ApiResponse($rbody, $rcode, $rheaders, $json); |
|
122 |
+ return [$resp, $myApiKey]; |
|
123 |
+ } |
|
124 |
+ |
|
125 |
+ /** |
|
126 |
+ * @param string $rbody A JSON string. |
|
127 |
+ * @param int $rcode |
|
128 |
+ * @param array $rheaders |
|
129 |
+ * @param array $resp |
|
130 |
+ * |
|
131 |
+ * @throws Error\InvalidRequest if the error is caused by the user. |
|
132 |
+ * @throws Error\Authentication if the error is caused by a lack of |
|
133 |
+ * permissions. |
|
134 |
+ * @throws Error\Permission if the error is caused by insufficient |
|
135 |
+ * permissions. |
|
136 |
+ * @throws Error\Card if the error is the error code is 402 (payment |
|
137 |
+ * required) |
|
138 |
+ * @throws Error\InvalidRequest if the error is caused by the user. |
|
139 |
+ * @throws Error\Idempotency if the error is caused by an idempotency key. |
|
140 |
+ * @throws Error\Permission if the error is caused by insufficient |
|
141 |
+ * permissions. |
|
142 |
+ * @throws Error\RateLimit if the error is caused by too many requests |
|
143 |
+ * hitting the API. |
|
144 |
+ * @throws Error\Api otherwise. |
|
145 |
+ */ |
|
146 |
+ public function handleErrorResponse($rbody, $rcode, $rheaders, $resp) |
|
147 |
+ { |
|
148 |
+ if (!is_array($resp) || !isset($resp['error'])) { |
|
149 |
+ $msg = "Invalid response object from API: $rbody " |
|
150 |
+ . "(HTTP response code was $rcode)"; |
|
151 |
+ throw new Error\Api($msg, $rcode, $rbody, $resp, $rheaders); |
|
152 |
+ } |
|
153 |
+ |
|
154 |
+ $errorData = $resp['error']; |
|
155 |
+ |
|
156 |
+ #echo $rbody;exit; |
|
157 |
+ |
|
158 |
+ $error = null; |
|
159 |
+ if (!$error) { |
|
160 |
+ $error = self::_specificAPIError($rbody, $rcode, $rheaders, $resp, $errorData); |
|
161 |
+ } |
|
162 |
+ |
|
163 |
+ throw $error; |
|
164 |
+ } |
|
165 |
+ |
|
166 |
+ /** |
|
167 |
+ * @static |
|
168 |
+ * |
|
169 |
+ * @param string $rbody |
|
170 |
+ * @param int $rcode |
|
171 |
+ * @param array $rheaders |
|
172 |
+ * @param array $resp |
|
173 |
+ * @param array $errorData |
|
174 |
+ * |
|
175 |
+ * @return Error\RateLimit|Error\Idempotency|Error\InvalidRequest|Error\Authentication|Error\Card|Error\Permission|Error\Api |
|
176 |
+ */ |
|
177 |
+ private static function _specificAPIError($rbody, $rcode, $rheaders, $resp, $errorData) |
|
178 |
+ { |
|
179 |
+ $msg = isset($errorData['message']) ? $errorData['message'] : null; |
|
180 |
+ $param = isset($errorData['param']) ? $errorData['param'] : null; |
|
181 |
+ $code = isset($errorData['code']) ? $errorData['code'] : null; |
|
182 |
+ $type = isset($errorData['type']) ? $errorData['type'] : null; |
|
183 |
+ |
|
184 |
+ switch ($rcode) { |
|
185 |
+ case 400: |
|
186 |
+ // 'rate_limit' code is deprecated, but left here for backwards compatibility |
|
187 |
+ // for API versions earlier than 2015-09-08 |
|
188 |
+ if ($code == 'rate_limit') { |
|
189 |
+ return new Error\RateLimit($msg, $param, $rcode, $rbody, $resp, $rheaders); |
|
190 |
+ } |
|
191 |
+ if ($type == 'idempotency_error') { |
|
192 |
+ return new Error\Idempotency($msg, $rcode, $rbody, $resp, $rheaders); |
|
193 |
+ } |
|
194 |
+ |
|
195 |
+ // intentional fall-through |
|
196 |
+ // no break |
|
197 |
+ case 404: |
|
198 |
+ return new Error\InvalidRequest($msg, $param, $rcode, $rbody, $resp, $rheaders); |
|
199 |
+ case 401: |
|
200 |
+ return new Error\Authentication($msg, $rcode, $rbody, $resp, $rheaders); |
|
201 |
+ case 402: |
|
202 |
+ return new Error\Card($msg, $param, $code, $rcode, $rbody, $resp, $rheaders); |
|
203 |
+ case 403: |
|
204 |
+ return new Error\Permission($msg, $rcode, $rbody, $resp, $rheaders); |
|
205 |
+ case 429: |
|
206 |
+ return new Error\RateLimit($msg, $param, $rcode, $rbody, $resp, $rheaders); |
|
207 |
+ default: |
|
208 |
+ return new Error\Api($msg, $rcode, $rbody, $resp, $rheaders); |
|
209 |
+ } |
|
210 |
+ } |
|
211 |
+ |
|
212 |
+ /** |
|
213 |
+ * @static |
|
214 |
+ * |
|
215 |
+ * @param null|array $appInfo |
|
216 |
+ * |
|
217 |
+ * @return null|string |
|
218 |
+ */ |
|
219 |
+ private static function _formatAppInfo($appInfo) |
|
220 |
+ { |
|
221 |
+ if ($appInfo !== null) { |
|
222 |
+ $string = $appInfo['name']; |
|
223 |
+ if ($appInfo['version'] !== null) { |
|
224 |
+ $string .= '/' . $appInfo['version']; |
|
225 |
+ } |
|
226 |
+ if ($appInfo['url'] !== null) { |
|
227 |
+ $string .= ' (' . $appInfo['url'] . ')'; |
|
228 |
+ } |
|
229 |
+ return $string; |
|
230 |
+ } else { |
|
231 |
+ return null; |
|
232 |
+ } |
|
233 |
+ } |
|
234 |
+ |
|
235 |
+ /** |
|
236 |
+ * @static |
|
237 |
+ * |
|
238 |
+ * @param string $apiKey |
|
239 |
+ * @param null $clientInfo |
|
240 |
+ * |
|
241 |
+ * @return array |
|
242 |
+ */ |
|
243 |
+ private static function _defaultHeaders($apiKey, $clientInfo = null) |
|
244 |
+ { |
|
245 |
+ $uaString = 'Telnyx/v2 PhpBindings/' . Telnyx::VERSION; |
|
246 |
+ |
|
247 |
+ $langVersion = phpversion(); |
|
248 |
+ $uname = php_uname(); |
|
249 |
+ |
|
250 |
+ $appInfo = Telnyx::getAppInfo(); |
|
251 |
+ $ua = [ |
|
252 |
+ 'bindings_version' => Telnyx::VERSION, |
|
253 |
+ 'lang' => 'php', |
|
254 |
+ 'lang_version' => $langVersion, |
|
255 |
+ 'publisher' => 'telnyx', |
|
256 |
+ 'uname' => $uname, |
|
257 |
+ ]; |
|
258 |
+ if ($clientInfo) { |
|
259 |
+ $ua = array_merge($clientInfo, $ua); |
|
260 |
+ } |
|
261 |
+ if ($appInfo !== null) { |
|
262 |
+ $uaString .= ' ' . self::_formatAppInfo($appInfo); |
|
263 |
+ $ua['application'] = $appInfo; |
|
264 |
+ } |
|
265 |
+ |
|
266 |
+ $defaultHeaders = [ |
|
267 |
+ 'X-Telnyx-Client-User-Agent' => json_encode($ua), |
|
268 |
+ 'User-Agent' => $uaString, |
|
269 |
+ 'Authorization' => 'Bearer ' . $apiKey, |
|
270 |
+ ]; |
|
271 |
+ return $defaultHeaders; |
|
272 |
+ } |
|
273 |
+ |
|
274 |
+ /** |
|
275 |
+ * @param string $method |
|
276 |
+ * @param string $url |
|
277 |
+ * @param array $params |
|
278 |
+ * @param array $headers |
|
279 |
+ * |
|
280 |
+ * @return array |
|
281 |
+ * @throws Error\Api |
|
282 |
+ * @throws Error\ApiConnection |
|
283 |
+ * @throws Error\Authentication |
|
284 |
+ */ |
|
285 |
+ private function _requestRaw($method, $url, $params, $headers) |
|
286 |
+ { |
|
287 |
+ $myApiKey = $this->_apiKey; |
|
288 |
+ if (!$myApiKey) { |
|
289 |
+ $myApiKey = Telnyx::$apiKey; |
|
290 |
+ } |
|
291 |
+ |
|
292 |
+ if (!$myApiKey) { |
|
293 |
+ $msg = 'No API key provided. (HINT: set your API key using ' |
|
294 |
+ . '"Telnyx::setApiKey(<API-KEY>)". You can generate API keys from ' |
|
295 |
+ . 'the Telnyx web interface. See https://developers.telnyx.com/docs/v2/development/authentication ' |
|
296 |
+ . 'for details, or email support@telnyx.com if you have any questions.'; |
|
297 |
+ throw new Error\Authentication($msg); |
|
298 |
+ } |
|
299 |
+ |
|
300 |
+ // Clients can supply arbitrary additional keys to be included in the |
|
301 |
+ // X-Telnyx-Client-User-Agent header via the optional getUserAgentInfo() |
|
302 |
+ // method |
|
303 |
+ $clientUAInfo = null; |
|
304 |
+ if (method_exists($this->httpClient(), 'getUserAgentInfo')) { |
|
305 |
+ $clientUAInfo = $this->httpClient()->getUserAgentInfo(); |
|
306 |
+ } |
|
307 |
+ |
|
308 |
+ $absUrl = $this->_apiBase.$url; |
|
309 |
+ $params = self::_encodeObjects($params); |
|
310 |
+ $defaultHeaders = $this->_defaultHeaders($myApiKey, $clientUAInfo); |
|
311 |
+ if (Telnyx::$apiVersion) { |
|
312 |
+ $defaultHeaders['Telnyx-Version'] = Telnyx::$apiVersion; |
|
313 |
+ } |
|
314 |
+ |
|
315 |
+ if (Telnyx::$accountId) { |
|
316 |
+ $defaultHeaders['Telnyx-Account'] = Telnyx::$accountId; |
|
317 |
+ } |
|
318 |
+ |
|
319 |
+ if (Telnyx::$enableTelemetry && self::$requestTelemetry != null) { |
|
320 |
+ $defaultHeaders["X-Telnyx-Client-Telemetry"] = self::_telemetryJson(self::$requestTelemetry); |
|
321 |
+ } |
|
322 |
+ |
|
323 |
+ $hasFile = false; |
|
324 |
+ $hasCurlFile = class_exists('\CURLFile', false); |
|
325 |
+ foreach ($params as $k => $v) { |
|
326 |
+ if (is_resource($v)) { |
|
327 |
+ $hasFile = true; |
|
328 |
+ $params[$k] = self::_processResourceParam($v, $hasCurlFile); |
|
329 |
+ } elseif ($hasCurlFile && $v instanceof \CURLFile) { |
|
330 |
+ $hasFile = true; |
|
331 |
+ } |
|
332 |
+ } |
|
333 |
+ |
|
334 |
+ if ($hasFile) { |
|
335 |
+ $defaultHeaders['Content-Type'] = 'multipart/form-data'; |
|
336 |
+ } else { |
|
337 |
+ $defaultHeaders['Content-Type'] = 'application/json'; |
|
338 |
+ } |
|
339 |
+ |
|
340 |
+ $combinedHeaders = array_merge($defaultHeaders, $headers); |
|
341 |
+ $rawHeaders = []; |
|
342 |
+ |
|
343 |
+ foreach ($combinedHeaders as $header => $value) { |
|
344 |
+ $rawHeaders[] = $header . ': ' . $value; |
|
345 |
+ } |
|
346 |
+ |
|
347 |
+ $requestStartMs = Util\Util::currentTimeMillis(); |
|
348 |
+ |
|
349 |
+ list($rbody, $rcode, $rheaders) = $this->httpClient()->request( |
|
350 |
+ $method, |
|
351 |
+ $absUrl, |
|
352 |
+ $rawHeaders, |
|
353 |
+ $params, |
|
354 |
+ $hasFile |
|
355 |
+ ); |
|
356 |
+ |
|
357 |
+ if (array_key_exists('request-id', $rheaders)) { |
|
358 |
+ self::$requestTelemetry = new RequestTelemetry( |
|
359 |
+ $rheaders['request-id'], |
|
360 |
+ Util\Util::currentTimeMillis() - $requestStartMs |
|
361 |
+ ); |
|
362 |
+ } |
|
363 |
+ |
|
364 |
+ return [$rbody, $rcode, $rheaders, $myApiKey]; |
|
365 |
+ } |
|
366 |
+ |
|
367 |
+ /** |
|
368 |
+ * @param resource $resource |
|
369 |
+ * @param bool $hasCurlFile |
|
370 |
+ * |
|
371 |
+ * @return \CURLFile|string |
|
372 |
+ * @throws Error\Api |
|
373 |
+ */ |
|
374 |
+ private function _processResourceParam($resource, $hasCurlFile) |
|
375 |
+ { |
|
376 |
+ if (get_resource_type($resource) !== 'stream') { |
|
377 |
+ throw new Error\Api( |
|
378 |
+ 'Attempted to upload a resource that is not a stream' |
|
379 |
+ ); |
|
380 |
+ } |
|
381 |
+ |
|
382 |
+ $metaData = stream_get_meta_data($resource); |
|
383 |
+ if ($metaData['wrapper_type'] !== 'plainfile') { |
|
384 |
+ throw new Error\Api( |
|
385 |
+ 'Only plainfile resource streams are supported' |
|
386 |
+ ); |
|
387 |
+ } |
|
388 |
+ |
|
389 |
+ if ($hasCurlFile) { |
|
390 |
+ // We don't have the filename or mimetype, but the API doesn't care |
|
391 |
+ return new \CURLFile($metaData['uri']); |
|
392 |
+ } else { |
|
393 |
+ return '@'.$metaData['uri']; |
|
394 |
+ } |
|
395 |
+ } |
|
396 |
+ |
|
397 |
+ /** |
|
398 |
+ * @param string $rbody |
|
399 |
+ * @param int $rcode |
|
400 |
+ * @param array $rheaders |
|
401 |
+ * |
|
402 |
+ * @return mixed |
|
403 |
+ * @throws Error\Api |
|
404 |
+ * @throws Error\Authentication |
|
405 |
+ * @throws Error\Card |
|
406 |
+ * @throws Error\InvalidRequest |
|
407 |
+ * @throws Error\Permission |
|
408 |
+ * @throws Error\RateLimit |
|
409 |
+ * @throws Error\Idempotency |
|
410 |
+ */ |
|
411 |
+ private function _interpretResponse($rbody, $rcode, $rheaders) |
|
412 |
+ { |
|
413 |
+ $resp = json_decode($rbody, true); |
|
414 |
+ |
|
415 |
+ // Move [data] to the parent node |
|
416 |
+ if (isset($resp['data'])) { |
|
417 |
+ $resp = $resp['data']; |
|
418 |
+ } |
|
419 |
+ |
|
420 |
+ $jsonError = json_last_error(); |
|
421 |
+ if ($resp === null && $jsonError !== JSON_ERROR_NONE) { |
|
422 |
+ $msg = "Invalid response body from API: $rbody " |
|
423 |
+ . "(HTTP response code was $rcode, json_last_error() was $jsonError)"; |
|
424 |
+ throw new Error\Api($msg, $rcode, $rbody); |
|
425 |
+ } |
|
426 |
+ |
|
427 |
+ if ($rcode < 200 || $rcode >= 300) { |
|
428 |
+ $this->handleErrorResponse($rbody, $rcode, $rheaders, $resp); |
|
429 |
+ } |
|
430 |
+ return $resp; |
|
431 |
+ } |
|
432 |
+ |
|
433 |
+ /** |
|
434 |
+ * @static |
|
435 |
+ * |
|
436 |
+ * @param HttpClient\ClientInterface $client |
|
437 |
+ */ |
|
438 |
+ public static function setHttpClient($client) |
|
439 |
+ { |
|
440 |
+ self::$_httpClient = $client; |
|
441 |
+ } |
|
442 |
+ |
|
443 |
+ /** |
|
444 |
+ * @static |
|
445 |
+ * |
|
446 |
+ * Resets any stateful telemetry data |
|
447 |
+ */ |
|
448 |
+ public static function resetTelemetry() |
|
449 |
+ { |
|
450 |
+ self::$requestTelemetry = null; |
|
451 |
+ } |
|
452 |
+ |
|
453 |
+ /** |
|
454 |
+ * @return HttpClient\ClientInterface |
|
455 |
+ */ |
|
456 |
+ private function httpClient() |
|
457 |
+ { |
|
458 |
+ if (!self::$_httpClient) { |
|
459 |
+ self::$_httpClient = HttpClient\CurlClient::instance(); |
|
460 |
+ } |
|
461 |
+ return self::$_httpClient; |
|
462 |
+ } |
|
463 |
+} |