• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

stripe / stripe-php / 11076452927

27 Sep 2024 07:17PM UTC coverage: 62.403% (-0.8%) from 63.204%
11076452927

push

github

web-flow
Adding support for the new Usage Billing APIs (#1750)

180 of 373 new or added lines in 21 files covered. (48.26%)

3 existing lines in 3 files now uncovered.

3552 of 5692 relevant lines covered (62.4%)

2.45 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

66.55
/lib/ApiRequestor.php
1
<?php
2

3
namespace Stripe;
4

5
/**
6
 * Class ApiRequestor.
7
 */
8
class ApiRequestor
9
{
10
    /**
11
     * @var null|string
12
     */
13
    private $_apiKey;
14

15
    /**
16
     * @var string
17
     */
18
    private $_apiBase;
19

20
    /**
21
     * @var null|array
22
     */
23
    private $_appInfo;
24

25
    /**
26
     * @var HttpClient\ClientInterface
27
     */
28
    private static $_httpClient;
29
    /**
30
     * @var HttpClient\StreamingClientInterface
31
     */
32
    private static $_streamingHttpClient;
33

34
    /**
35
     * @var RequestTelemetry
36
     */
37
    private static $requestTelemetry;
38

39
    private static $OPTIONS_KEYS = ['api_key', 'idempotency_key', 'stripe_account', 'stripe_context', 'stripe_version', 'api_base'];
40

41
    /**
42
     * ApiRequestor constructor.
43
     *
44
     * @param null|string $apiKey
45
     * @param null|string $apiBase
46
     * @param null|array $appInfo
47
     */
48
    public function __construct($apiKey = null, $apiBase = null, $appInfo = null)
24✔
49
    {
50
        $this->_apiKey = $apiKey;
24✔
51
        if (!$apiBase) {
24✔
52
            $apiBase = Stripe::$apiBase;
1✔
53
        }
54
        $this->_apiBase = $apiBase;
24✔
55
        $this->_appInfo = $appInfo;
24✔
56
    }
57

58
    /**
59
     * Creates a telemetry json blob for use in 'X-Stripe-Client-Telemetry' headers.
60
     *
61
     * @static
62
     *
63
     * @param RequestTelemetry $requestTelemetry
64
     *
65
     * @return string
66
     */
67
    private static function _telemetryJson($requestTelemetry)
×
68
    {
69
        $payload = [
×
70
            'last_request_metrics' => [
×
71
                'request_id' => $requestTelemetry->requestId,
×
72
                'request_duration_ms' => $requestTelemetry->requestDuration,
×
73
            ],
×
74
        ];
×
75
        if (\count($requestTelemetry->usage) > 0) {
×
76
            $payload['last_request_metrics']['usage'] = $requestTelemetry->usage;
×
77
        }
78

79
        $result = \json_encode($payload);
×
80
        if (false !== $result) {
×
81
            return $result;
×
82
        }
83
        Stripe::getLogger()->error('Serializing telemetry payload failed!');
×
84

85
        return '{}';
×
86
    }
87

88
    /**
89
     * @static
90
     *
91
     * @param ApiResource|array|bool|mixed $d
92
     *
93
     * @return ApiResource|array|mixed|string
94
     */
95
    private static function _encodeObjects($d)
20✔
96
    {
97
        if ($d instanceof ApiResource) {
20✔
98
            return Util\Util::utf8($d->id);
1✔
99
        }
100
        if (true === $d) {
20✔
101
            return 'true';
1✔
102
        }
103
        if (false === $d) {
20✔
104
            return 'false';
1✔
105
        }
106
        if (\is_array($d)) {
20✔
107
            $res = [];
20✔
108
            foreach ($d as $k => $v) {
20✔
109
                $res[$k] = self::_encodeObjects($v);
1✔
110
            }
111

112
            return $res;
20✔
113
        }
114

115
        return Util\Util::utf8($d);
1✔
116
    }
117

118
    /**
119
     * @param 'delete'|'get'|'post'     $method
120
     * @param string     $url
121
     * @param null|array $params
122
     * @param null|array $headers
123
     * @param 'v1'|'v2' $apiMode
124
     * @param string[] $usage
125
     *
126
     * @throws Exception\ApiErrorException
127
     *
128
     * @return array tuple containing (ApiReponse, API key)
129
     */
130
    public function request($method, $url, $params = null, $headers = null, $apiMode = 'v1', $usage = [])
23✔
131
    {
132
        $params = $params ?: [];
23✔
133
        $headers = $headers ?: [];
23✔
134
        list($rbody, $rcode, $rheaders, $myApiKey) =
23✔
135
            $this->_requestRaw($method, $url, $params, $headers, $apiMode, $usage);
23✔
136
        $json = $this->_interpretResponse($rbody, $rcode, $rheaders, $apiMode);
22✔
137
        $resp = new ApiResponse($rbody, $rcode, $rheaders, $json);
6✔
138

139
        return [$resp, $myApiKey];
6✔
140
    }
141

142
    /**
143
     * @param 'delete'|'get'|'post' $method
144
     * @param string     $url
145
     * @param callable $readBodyChunkCallable
146
     * @param null|array $params
147
     * @param null|array $headers
148
     * @param 'v1'|'v2' $apiMode
149
     * @param string[] $usage
150
     *
151
     * @throws Exception\ApiErrorException
152
     */
NEW
153
    public function requestStream($method, $url, $readBodyChunkCallable, $params = null, $headers = null, $apiMode = 'v1', $usage = [])
×
154
    {
155
        $params = $params ?: [];
×
156
        $headers = $headers ?: [];
×
157
        list($rbody, $rcode, $rheaders, $myApiKey) =
×
NEW
158
            $this->_requestRawStreaming($method, $url, $params, $headers, $apiMode, $usage, $readBodyChunkCallable);
×
159
        if ($rcode >= 300) {
×
NEW
160
            $this->_interpretResponse($rbody, $rcode, $rheaders, $apiMode);
×
161
        }
162
    }
163

164
    /**
165
     * @param string $rbody a JSON string
166
     * @param int $rcode
167
     * @param array $rheaders
168
     * @param array $resp
169
     * @param 'v1'|'v2' $apiMode
170
     *
171
     * @throws Exception\UnexpectedValueException
172
     * @throws Exception\ApiErrorException
173
     */
174
    public function handleErrorResponse($rbody, $rcode, $rheaders, $resp, $apiMode)
16✔
175
    {
176
        if (!\is_array($resp) || !isset($resp['error'])) {
16✔
177
            $msg = "Invalid response object from API: {$rbody} "
×
178
                . "(HTTP response code was {$rcode})";
×
179

180
            throw new Exception\UnexpectedValueException($msg);
×
181
        }
182

183
        $errorData = $resp['error'];
16✔
184

185
        $error = null;
16✔
186

187
        if (\is_string($errorData)) {
16✔
188
            $error = self::_specificOAuthError($rbody, $rcode, $rheaders, $resp, $errorData);
6✔
189
        }
190
        if (!$error) {
16✔
191
            $error = 'v1' === $apiMode ? self::_specificV1APIError($rbody, $rcode, $rheaders, $resp, $errorData) : self::_specificV2APIError($rbody, $rcode, $rheaders, $resp, $errorData);
10✔
192
        }
193

194
        throw $error;
16✔
195
    }
196

197
    /**
198
     * @static
199
     *
200
     * @param string $rbody
201
     * @param int    $rcode
202
     * @param array  $rheaders
203
     * @param array  $resp
204
     * @param array  $errorData
205
     *
206
     * @return Exception\ApiErrorException
207
     */
208
    private static function _specificV1APIError($rbody, $rcode, $rheaders, $resp, $errorData)
9✔
209
    {
210
        $msg = isset($errorData['message']) ? $errorData['message'] : null;
9✔
211
        $param = isset($errorData['param']) ? $errorData['param'] : null;
9✔
212
        $code = isset($errorData['code']) ? $errorData['code'] : null;
9✔
213
        $type = isset($errorData['type']) ? $errorData['type'] : null;
9✔
214
        $declineCode = isset($errorData['decline_code']) ? $errorData['decline_code'] : null;
9✔
215

216
        switch ($rcode) {
217
            case 400:
9✔
218
                // 'rate_limit' code is deprecated, but left here for backwards compatibility
219
                // for API versions earlier than 2015-09-08
220
                if ('rate_limit' === $code) {
4✔
221
                    return Exception\RateLimitException::factory($msg, $rcode, $rbody, $resp, $rheaders, $code, $param);
1✔
222
                }
223
                if ('idempotency_error' === $type) {
3✔
224
                    return Exception\IdempotencyException::factory($msg, $rcode, $rbody, $resp, $rheaders, $code);
1✔
225
                }
226

227
            // no break
228
            case 404:
5✔
229
                return Exception\InvalidRequestException::factory($msg, $rcode, $rbody, $resp, $rheaders, $code, $param);
3✔
230

231
            case 401:
4✔
232
                return Exception\AuthenticationException::factory($msg, $rcode, $rbody, $resp, $rheaders, $code);
1✔
233

234
            case 402:
3✔
235
                return Exception\CardException::factory($msg, $rcode, $rbody, $resp, $rheaders, $code, $declineCode, $param);
1✔
236

237
            case 403:
2✔
238
                return Exception\PermissionException::factory($msg, $rcode, $rbody, $resp, $rheaders, $code);
1✔
239

240
            case 429:
1✔
241
                return Exception\RateLimitException::factory($msg, $rcode, $rbody, $resp, $rheaders, $code, $param);
1✔
242

243
            default:
244
                return Exception\UnknownApiErrorException::factory($msg, $rcode, $rbody, $resp, $rheaders, $code);
×
245
        }
246
    }
247

248
    /**
249
     * @static
250
     *
251
     * @param string $rbody
252
     * @param int    $rcode
253
     * @param array  $rheaders
254
     * @param array  $resp
255
     * @param array  $errorData
256
     *
257
     * @return Exception\ApiErrorException
258
     */
259
    private static function _specificV2APIError($rbody, $rcode, $rheaders, $resp, $errorData)
2✔
260
    {
261
        $msg = isset($errorData['message']) ? $errorData['message'] : null;
2✔
262
        $code = isset($errorData['code']) ? $errorData['code'] : null;
2✔
263
        $type = isset($errorData['type']) ? $errorData['type'] : null;
2✔
264

265
        switch ($type) {
266
            case 'idempotency_error':
2✔
NEW
267
                return Exception\IdempotencyException::factory($msg, $rcode, $rbody, $resp, $rheaders, $code);
×
268
            // The beginning of the section generated from our OpenAPI spec
269
            case 'temporary_session_expired':
2✔
270
                return Exception\TemporarySessionExpiredException::factory(
1✔
271
                    $msg,
1✔
272
                    $rcode,
1✔
273
                    $rbody,
1✔
274
                    $resp,
1✔
275
                    $rheaders,
1✔
276
                    $code
1✔
277
                );
1✔
278

279
            // The end of the section generated from our OpenAPI spec
280
            default:
281
                return self::_specificV1APIError($rbody, $rcode, $rheaders, $resp, $errorData);
1✔
282
        }
283
    }
284

285
    /**
286
     * @static
287
     *
288
     * @param bool|string $rbody
289
     * @param int         $rcode
290
     * @param array       $rheaders
291
     * @param array       $resp
292
     * @param string      $errorCode
293
     *
294
     * @return Exception\OAuth\OAuthErrorException
295
     */
296
    private static function _specificOAuthError($rbody, $rcode, $rheaders, $resp, $errorCode)
6✔
297
    {
298
        $description = isset($resp['error_description']) ? $resp['error_description'] : $errorCode;
6✔
299

300
        switch ($errorCode) {
301
            case 'invalid_client':
6✔
302
                return Exception\OAuth\InvalidClientException::factory($description, $rcode, $rbody, $resp, $rheaders, $errorCode);
1✔
303

304
            case 'invalid_grant':
5✔
305
                return Exception\OAuth\InvalidGrantException::factory($description, $rcode, $rbody, $resp, $rheaders, $errorCode);
1✔
306

307
            case 'invalid_request':
4✔
308
                return Exception\OAuth\InvalidRequestException::factory($description, $rcode, $rbody, $resp, $rheaders, $errorCode);
1✔
309

310
            case 'invalid_scope':
3✔
311
                return Exception\OAuth\InvalidScopeException::factory($description, $rcode, $rbody, $resp, $rheaders, $errorCode);
1✔
312

313
            case 'unsupported_grant_type':
2✔
314
                return Exception\OAuth\UnsupportedGrantTypeException::factory($description, $rcode, $rbody, $resp, $rheaders, $errorCode);
1✔
315

316
            case 'unsupported_response_type':
1✔
317
                return Exception\OAuth\UnsupportedResponseTypeException::factory($description, $rcode, $rbody, $resp, $rheaders, $errorCode);
1✔
318

319
            default:
320
                return Exception\OAuth\UnknownOAuthErrorException::factory($description, $rcode, $rbody, $resp, $rheaders, $errorCode);
×
321
        }
322
    }
323

324
    /**
325
     * @static
326
     *
327
     * @param null|array $appInfo
328
     *
329
     * @return null|string
330
     */
331
    private static function _formatAppInfo($appInfo)
23✔
332
    {
333
        if (null !== $appInfo) {
23✔
334
            $string = $appInfo['name'];
23✔
335
            if (\array_key_exists('version', $appInfo) && null !== $appInfo['version']) {
23✔
336
                $string .= '/' . $appInfo['version'];
23✔
337
            }
338
            if (\array_key_exists('url', $appInfo) && null !== $appInfo['url']) {
23✔
339
                $string .= ' (' . $appInfo['url'] . ')';
23✔
340
            }
341

342
            return $string;
23✔
343
        }
344

345
        return null;
×
346
    }
347

348
    /**
349
     * @static
350
     *
351
     * @param string $disableFunctionsOutput - String value of the 'disable_function' setting, as output by \ini_get('disable_functions')
352
     * @param string $functionName - Name of the function we are interesting in seeing whether or not it is disabled
353
     *
354
     * @return bool
355
     */
356
    private static function _isDisabled($disableFunctionsOutput, $functionName)
24✔
357
    {
358
        $disabledFunctions = \explode(',', $disableFunctionsOutput);
24✔
359
        foreach ($disabledFunctions as $disabledFunction) {
24✔
360
            if (\trim($disabledFunction) === $functionName) {
24✔
361
                return true;
1✔
362
            }
363
        }
364

365
        return false;
24✔
366
    }
367

368
    /**
369
     * @static
370
     *
371
     * @param string     $apiKey the Stripe API key, to be used in regular API requests
372
     * @param null       $clientInfo client user agent information
373
     * @param null       $appInfo information to identify a plugin that integrates Stripe using this library
374
     * @param 'v1'|'v2' $apiMode
375
     *
376
     * @return array
377
     */
378
    private static function _defaultHeaders($apiKey, $clientInfo = null, $appInfo = null, $apiMode = 'v1')
23✔
379
    {
380
        $uaString = "Stripe/{$apiMode} PhpBindings/" . Stripe::VERSION;
23✔
381

382
        $langVersion = \PHP_VERSION;
23✔
383
        $uname_disabled = self::_isDisabled(\ini_get('disable_functions'), 'php_uname');
23✔
384
        $uname = $uname_disabled ? '(disabled)' : \php_uname();
23✔
385

386
        // Fallback to global configuration to maintain backwards compatibility.
387
        $appInfo = $appInfo ?: Stripe::getAppInfo();
23✔
388
        $ua = [
23✔
389
            'bindings_version' => Stripe::VERSION,
23✔
390
            'lang' => 'php',
23✔
391
            'lang_version' => $langVersion,
23✔
392
            'publisher' => 'stripe',
23✔
393
            'uname' => $uname,
23✔
394
        ];
23✔
395
        if ($clientInfo) {
23✔
396
            $ua = \array_merge($clientInfo, $ua);
1✔
397
        }
398
        if (null !== $appInfo) {
23✔
399
            $uaString .= ' ' . self::_formatAppInfo($appInfo);
23✔
400
            $ua['application'] = $appInfo;
23✔
401
        }
402

403
        return [
23✔
404
            'X-Stripe-Client-User-Agent' => \json_encode($ua),
23✔
405
            'User-Agent' => $uaString,
23✔
406
            'Authorization' => 'Bearer ' . $apiKey,
23✔
407
            'Stripe-Version' => Stripe::getApiVersion(),
23✔
408
        ];
23✔
409
    }
410

411
    /**
412
     * @param 'delete'|'get'|'post' $method
413
     * @param string $url
414
     * @param array $params
415
     * @param array $headers
416
     * @param 'v1'|'v2' $apiMode
417
     */
418
    private function _prepareRequest($method, $url, $params, $headers, $apiMode)
23✔
419
    {
420
        $myApiKey = $this->_apiKey;
23✔
421
        if (!$myApiKey) {
23✔
422
            $myApiKey = Stripe::$apiKey;
20✔
423
        }
424

425
        if (!$myApiKey) {
23✔
426
            $msg = 'No API key provided.  (HINT: set your API key using '
1✔
427
                . '"Stripe::setApiKey(<API-KEY>)".  You can generate API keys from '
1✔
428
                . 'the Stripe web interface.  See https://stripe.com/api for '
1✔
429
                . 'details, or email support@stripe.com if you have any questions.';
1✔
430

431
            throw new Exception\AuthenticationException($msg);
1✔
432
        }
433

434
        // Clients can supply arbitrary additional keys to be included in the
435
        // X-Stripe-Client-User-Agent header via the optional getUserAgentInfo()
436
        // method
437
        $clientUAInfo = null;
22✔
438
        if (\method_exists($this->httpClient(), 'getUserAgentInfo')) {
22✔
439
            $clientUAInfo = $this->httpClient()->getUserAgentInfo();
×
440
        }
441

442
        if ($params && \is_array($params)) {
22✔
443
            $optionKeysInParams = \array_filter(
×
444
                self::$OPTIONS_KEYS,
×
445
                function ($key) use ($params) {
×
446
                    return \array_key_exists($key, $params);
×
447
                }
×
448
            );
×
449
            if (\count($optionKeysInParams) > 0) {
×
450
                $message = \sprintf('Options found in $params: %s. Options should '
×
451
                    . 'be passed in their own array after $params. (HINT: pass an '
×
452
                    . 'empty array to $params if you do not have any.)', \implode(', ', $optionKeysInParams));
×
453
                \trigger_error($message, \E_USER_WARNING);
×
454
            }
455
        }
456

457
        $absUrl = $this->_apiBase . $url;
22✔
458
        if ('v1' === $apiMode) {
22✔
459
            $params = self::_encodeObjects($params);
19✔
460
        }
461
        $defaultHeaders = $this->_defaultHeaders($myApiKey, $clientUAInfo, $this->_appInfo, $apiMode);
22✔
462

463
        if (Stripe::$accountId) {
22✔
464
            $defaultHeaders['Stripe-Account'] = Stripe::$accountId;
1✔
465
        }
466

467
        if (Stripe::$enableTelemetry && null !== self::$requestTelemetry) {
22✔
468
            $defaultHeaders['X-Stripe-Client-Telemetry'] = self::_telemetryJson(self::$requestTelemetry);
×
469
        }
470

471
        $hasFile = false;
22✔
472
        foreach ($params as $k => $v) {
22✔
473
            if (\is_resource($v)) {
×
474
                $hasFile = true;
×
475
                $params[$k] = self::_processResourceParam($v);
×
476
            } elseif ($v instanceof \CURLFile) {
×
477
                $hasFile = true;
×
478
            }
479
        }
480

481
        if ($hasFile) {
22✔
482
            $defaultHeaders['Content-Type'] = 'multipart/form-data';
×
483
        } elseif ('v2' === $apiMode) {
22✔
484
            $defaultHeaders['Content-Type'] = 'application/json';
3✔
485
        } elseif ('v1' === $apiMode) {
19✔
486
            $defaultHeaders['Content-Type'] = 'application/x-www-form-urlencoded';
19✔
487
        } else {
NEW
488
            throw new Exception\InvalidArgumentException('Unknown API mode: ' . $apiMode);
×
489
        }
490

491
        $combinedHeaders = \array_merge($defaultHeaders, $headers);
22✔
492
        $rawHeaders = [];
22✔
493

494
        foreach ($combinedHeaders as $header => $value) {
22✔
495
            $rawHeaders[] = $header . ': ' . $value;
22✔
496
        }
497

498
        return [$absUrl, $rawHeaders, $params, $hasFile, $myApiKey];
22✔
499
    }
500

501
    /**
502
     * @param 'delete'|'get'|'post' $method
503
     * @param string $url
504
     * @param array $params
505
     * @param array $headers
506
     * @param 'v1'|'v2' $apiMode
507
     * @param string[] $usage
508
     *
509
     * @throws Exception\AuthenticationException
510
     * @throws Exception\ApiConnectionException
511
     *
512
     * @return array
513
     */
514
    private function _requestRaw($method, $url, $params, $headers, $apiMode, $usage)
23✔
515
    {
516
        list($absUrl, $rawHeaders, $params, $hasFile, $myApiKey) = $this->_prepareRequest($method, $url, $params, $headers, $apiMode);
23✔
517

518
        $requestStartMs = Util\Util::currentTimeMillis();
22✔
519

520
        list($rbody, $rcode, $rheaders) = $this->httpClient()->request(
22✔
521
            $method,
22✔
522
            $absUrl,
22✔
523
            $rawHeaders,
22✔
524
            $params,
22✔
525
            $hasFile,
22✔
526
            $apiMode
22✔
527
        );
22✔
528

529
        if (
530
            isset($rheaders['request-id'])
22✔
531
            && \is_string($rheaders['request-id'])
22✔
532
            && '' !== $rheaders['request-id']
22✔
533
        ) {
534
            self::$requestTelemetry = new RequestTelemetry(
×
535
                $rheaders['request-id'],
×
536
                Util\Util::currentTimeMillis() - $requestStartMs,
×
537
                $usage
×
538
            );
×
539
        }
540

541
        return [$rbody, $rcode, $rheaders, $myApiKey];
22✔
542
    }
543

544
    /**
545
     * @param 'delete'|'get'|'post' $method
546
     * @param string $url
547
     * @param array $params
548
     * @param array $headers
549
     * @param string[] $usage
550
     * @param callable $readBodyChunkCallable
551
     * @param 'v1'|'v2' $apiMode
552
     *
553
     * @throws Exception\AuthenticationException
554
     * @throws Exception\ApiConnectionException
555
     *
556
     * @return array
557
     */
NEW
558
    private function _requestRawStreaming($method, $url, $params, $headers, $apiMode, $usage, $readBodyChunkCallable)
×
559
    {
NEW
560
        list($absUrl, $rawHeaders, $params, $hasFile, $myApiKey) = $this->_prepareRequest($method, $url, $params, $headers, $apiMode);
×
561

562
        $requestStartMs = Util\Util::currentTimeMillis();
×
563

564
        list($rbody, $rcode, $rheaders) = $this->streamingHttpClient()->requestStream(
×
565
            $method,
×
566
            $absUrl,
×
567
            $rawHeaders,
×
568
            $params,
×
569
            $hasFile,
×
570
            $readBodyChunkCallable
×
571
        );
×
572

573
        if (
574
            isset($rheaders['request-id'])
×
575
            && \is_string($rheaders['request-id'])
×
576
            && '' !== $rheaders['request-id']
×
577
        ) {
578
            self::$requestTelemetry = new RequestTelemetry(
×
579
                $rheaders['request-id'],
×
580
                Util\Util::currentTimeMillis() - $requestStartMs
×
581
            );
×
582
        }
583

584
        return [$rbody, $rcode, $rheaders, $myApiKey];
×
585
    }
586

587
    /**
588
     * @param resource $resource
589
     *
590
     * @throws Exception\InvalidArgumentException
591
     *
592
     * @return \CURLFile|string
593
     */
594
    private function _processResourceParam($resource)
×
595
    {
596
        if ('stream' !== \get_resource_type($resource)) {
×
597
            throw new Exception\InvalidArgumentException(
×
598
                'Attempted to upload a resource that is not a stream'
×
599
            );
×
600
        }
601

602
        $metaData = \stream_get_meta_data($resource);
×
603
        if ('plainfile' !== $metaData['wrapper_type']) {
×
604
            throw new Exception\InvalidArgumentException(
×
605
                'Only plainfile resource streams are supported'
×
606
            );
×
607
        }
608

609
        // We don't have the filename or mimetype, but the API doesn't care
610
        return new \CURLFile($metaData['uri']);
×
611
    }
612

613
    /**
614
     * @param string $rbody
615
     * @param int    $rcode
616
     * @param array  $rheaders
617
     * @param 'v1'|'v2'  $apiMode
618
     *
619
     * @throws Exception\UnexpectedValueException
620
     * @throws Exception\ApiErrorException
621
     *
622
     * @return array
623
     */
624
    private function _interpretResponse($rbody, $rcode, $rheaders, $apiMode)
22✔
625
    {
626
        $resp = \json_decode($rbody, true);
22✔
627
        $jsonError = \json_last_error();
22✔
628
        if (null === $resp && \JSON_ERROR_NONE !== $jsonError) {
22✔
629
            $msg = "Invalid response body from API: {$rbody} "
×
630
                . "(HTTP response code was {$rcode}, json_last_error() was {$jsonError})";
×
631

632
            throw new Exception\UnexpectedValueException($msg, $rcode);
×
633
        }
634

635
        if ($rcode < 200 || $rcode >= 300) {
22✔
636
            $this->handleErrorResponse($rbody, $rcode, $rheaders, $resp, $apiMode);
16✔
637
        }
638

639
        return $resp;
6✔
640
    }
641

642
    /**
643
     * @static
644
     *
645
     * @param HttpClient\ClientInterface $client
646
     */
647
    public static function setHttpClient($client)
27✔
648
    {
649
        self::$_httpClient = $client;
27✔
650
    }
651

652
    /**
653
     * @static
654
     *
655
     * @param HttpClient\StreamingClientInterface $client
656
     */
657
    public static function setStreamingHttpClient($client)
27✔
658
    {
659
        self::$_streamingHttpClient = $client;
27✔
660
    }
661

662
    /**
663
     * @static
664
     *
665
     * Resets any stateful telemetry data
666
     */
667
    public static function resetTelemetry()
×
668
    {
669
        self::$requestTelemetry = null;
×
670
    }
671

672
    /**
673
     * @return HttpClient\ClientInterface
674
     */
675
    private function httpClient()
23✔
676
    {
677
        if (!self::$_httpClient) {
23✔
678
            self::$_httpClient = HttpClient\CurlClient::instance();
×
679
        }
680

681
        return self::$_httpClient;
23✔
682
    }
683

684
    /**
685
     * @return HttpClient\StreamingClientInterface
686
     */
687
    private function streamingHttpClient()
×
688
    {
689
        if (!self::$_streamingHttpClient) {
×
690
            self::$_streamingHttpClient = HttpClient\CurlClient::instance();
×
691
        }
692

693
        return self::$_streamingHttpClient;
×
694
    }
695
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc