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

stripe / stripe-php / 9294232848

29 May 2024 11:01PM UTC coverage: 61.377% (+0.06%) from 61.314%
9294232848

push

github

web-flow
Merge pull request #1704 from stripe/helenye/merge-php-beta

Merge changes from stripe-php master

22 of 32 new or added lines in 2 files covered. (68.75%)

1 existing line in 1 file now uncovered.

2425 of 3951 relevant lines covered (61.38%)

3.21 hits per line

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

64.5
/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_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)
21✔
49
    {
50
        $this->_apiKey = $apiKey;
21✔
51
        if (!$apiBase) {
21✔
52
            $apiBase = Stripe::$apiBase;
1✔
53
        }
54
        $this->_apiBase = $apiBase;
21✔
55
        $this->_appInfo = $appInfo;
21✔
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 'preview'|'standard' $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 = 'standard', $usage = [])
20✔
131
    {
132
        $params = $params ?: [];
20✔
133
        $headers = $headers ?: [];
20✔
134
        list($rbody, $rcode, $rheaders, $myApiKey) =
20✔
135
            $this->_requestRaw($method, $url, $params, $headers, $apiMode, $usage);
20✔
136
        $json = $this->_interpretResponse($rbody, $rcode, $rheaders);
19✔
137
        $resp = new ApiResponse($rbody, $rcode, $rheaders, $json);
4✔
138

139
        return [$resp, $myApiKey];
4✔
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 'preview'|'standard' $apiMode
149
     * @param string[] $usage
150
     *
151
     * @throws Exception\ApiErrorException
152
     */
153
    public function requestStream($method, $url, $readBodyChunkCallable, $params = null, $headers = null, $apiMode = 'standard', $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) {
×
160
            $this->_interpretResponse($rbody, $rcode, $rheaders);
×
161
        }
162
    }
163

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

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

182
        $errorData = $resp['error'];
15✔
183

184
        $error = null;
15✔
185
        if (\is_string($errorData)) {
15✔
186
            $error = self::_specificOAuthError($rbody, $rcode, $rheaders, $resp, $errorData);
6✔
187
        }
188
        if (!$error) {
15✔
189
            $error = self::_specificAPIError($rbody, $rcode, $rheaders, $resp, $errorData);
9✔
190
        }
191

192
        throw $error;
15✔
193
    }
194

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

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

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

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

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

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

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

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

246
    /**
247
     * @static
248
     *
249
     * @param bool|string $rbody
250
     * @param int         $rcode
251
     * @param array       $rheaders
252
     * @param array       $resp
253
     * @param string      $errorCode
254
     *
255
     * @return Exception\OAuth\OAuthErrorException
256
     */
257
    private static function _specificOAuthError($rbody, $rcode, $rheaders, $resp, $errorCode)
6✔
258
    {
259
        $description = isset($resp['error_description']) ? $resp['error_description'] : $errorCode;
6✔
260

261
        switch ($errorCode) {
262
            case 'invalid_client':
6✔
263
                return Exception\OAuth\InvalidClientException::factory($description, $rcode, $rbody, $resp, $rheaders, $errorCode);
1✔
264

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

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

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

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

277
            case 'unsupported_response_type':
1✔
278
                return Exception\OAuth\UnsupportedResponseTypeException::factory($description, $rcode, $rbody, $resp, $rheaders, $errorCode);
1✔
279

280
            default:
281
                return Exception\OAuth\UnknownOAuthErrorException::factory($description, $rcode, $rbody, $resp, $rheaders, $errorCode);
×
282
        }
283
    }
284

285
    /**
286
     * @static
287
     *
288
     * @param null|array $appInfo
289
     *
290
     * @return null|string
291
     */
292
    private static function _formatAppInfo($appInfo)
20✔
293
    {
294
        if (null !== $appInfo) {
20✔
295
            $string = $appInfo['name'];
20✔
296
            if (\array_key_exists('version', $appInfo) && null !== $appInfo['version']) {
20✔
297
                $string .= '/' . $appInfo['version'];
20✔
298
            }
299
            if (\array_key_exists('url', $appInfo) && null !== $appInfo['url']) {
20✔
300
                $string .= ' (' . $appInfo['url'] . ')';
20✔
301
            }
302

303
            return $string;
20✔
304
        }
305

306
        return null;
×
307
    }
308

309
    /**
310
     * @static
311
     *
312
     * @param string $disableFunctionsOutput - String value of the 'disable_function' setting, as output by \ini_get('disable_functions')
313
     * @param string $functionName - Name of the function we are interesting in seeing whether or not it is disabled
314
     *
315
     * @return bool
316
     */
317
    private static function _isDisabled($disableFunctionsOutput, $functionName)
21✔
318
    {
319
        $disabledFunctions = \explode(',', $disableFunctionsOutput);
21✔
320
        foreach ($disabledFunctions as $disabledFunction) {
21✔
321
            if (\trim($disabledFunction) === $functionName) {
21✔
322
                return true;
1✔
323
            }
324
        }
325

326
        return false;
21✔
327
    }
328

329
    /**
330
     * @static
331
     *
332
     * @param string     $apiKey the Stripe API key, to be used in regular API requests
333
     * @param null       $clientInfo client user agent information
334
     * @param null       $appInfo information to identify a plugin that integrates Stripe using this library
335
     *
336
     * @return array
337
     */
338
    private static function _defaultHeaders($apiKey, $clientInfo = null, $appInfo = null)
20✔
339
    {
340
        $uaString = 'Stripe/v1 PhpBindings/' . Stripe::VERSION;
20✔
341

342
        $langVersion = \PHP_VERSION;
20✔
343
        $uname_disabled = self::_isDisabled(\ini_get('disable_functions'), 'php_uname');
20✔
344
        $uname = $uname_disabled ? '(disabled)' : \php_uname();
20✔
345

346
        // Fallback to global configuration to maintain backwards compatibility.
347
        $appInfo = $appInfo ?: Stripe::getAppInfo();
20✔
348
        $ua = [
20✔
349
            'bindings_version' => Stripe::VERSION,
20✔
350
            'lang' => 'php',
20✔
351
            'lang_version' => $langVersion,
20✔
352
            'publisher' => 'stripe',
20✔
353
            'uname' => $uname,
20✔
354
        ];
20✔
355
        if ($clientInfo) {
20✔
356
            $ua = \array_merge($clientInfo, $ua);
1✔
357
        }
358
        if (null !== $appInfo) {
20✔
359
            $uaString .= ' ' . self::_formatAppInfo($appInfo);
20✔
360
            $ua['application'] = $appInfo;
20✔
361
        }
362

363
        return [
20✔
364
            'X-Stripe-Client-User-Agent' => \json_encode($ua),
20✔
365
            'User-Agent' => $uaString,
20✔
366
            'Authorization' => 'Bearer ' . $apiKey,
20✔
367
            'Stripe-Version' => Stripe::getApiVersion(),
20✔
368
        ];
20✔
369
    }
370

371
    /**
372
     * @param 'delete'|'get'|'post' $method
373
     * @param string $url
374
     * @param array $params
375
     * @param array $headers
376
     * @param 'preview'|'standard' $apiMode
377
     */
378
    private function _prepareRequest($method, $url, $params, $headers, $apiMode)
20✔
379
    {
380
        $myApiKey = $this->_apiKey;
20✔
381
        if (!$myApiKey) {
20✔
382
            $myApiKey = Stripe::$apiKey;
20✔
383
        }
384

385
        if (!$myApiKey) {
20✔
386
            $msg = 'No API key provided.  (HINT: set your API key using '
1✔
387
                . '"Stripe::setApiKey(<API-KEY>)".  You can generate API keys from '
1✔
388
                . 'the Stripe web interface.  See https://stripe.com/api for '
1✔
389
                . 'details, or email support@stripe.com if you have any questions.';
1✔
390

391
            throw new Exception\AuthenticationException($msg);
1✔
392
        }
393

394
        // Clients can supply arbitrary additional keys to be included in the
395
        // X-Stripe-Client-User-Agent header via the optional getUserAgentInfo()
396
        // method
397
        $clientUAInfo = null;
19✔
398
        if (\method_exists($this->httpClient(), 'getUserAgentInfo')) {
19✔
399
            $clientUAInfo = $this->httpClient()->getUserAgentInfo();
×
400
        }
401

402
        if ($params && \is_array($params)) {
19✔
403
            $optionKeysInParams = \array_filter(
×
404
                self::$OPTIONS_KEYS,
×
405
                function ($key) use ($params) {
×
406
                    return \array_key_exists($key, $params);
×
407
                }
×
408
            );
×
409
            if (\count($optionKeysInParams) > 0) {
×
410
                $message = \sprintf('Options found in $params: %s. Options should '
×
NEW
411
                    . 'be passed in their own array after $params. (HINT: pass an '
×
NEW
412
                    . 'empty array to $params if you do not have any.)', \implode(', ', $optionKeysInParams));
×
UNCOV
413
                \trigger_error($message, \E_USER_WARNING);
×
414
            }
415
        }
416

417
        $absUrl = $this->_apiBase . $url;
19✔
418
        if ('standard' === $apiMode) {
19✔
419
            $params = self::_encodeObjects($params);
19✔
420
        }
421
        $defaultHeaders = $this->_defaultHeaders($myApiKey, $clientUAInfo, $this->_appInfo);
19✔
422

423
        if (Stripe::$accountId) {
19✔
424
            $defaultHeaders['Stripe-Account'] = Stripe::$accountId;
1✔
425
        }
426

427
        if (Stripe::$enableTelemetry && null !== self::$requestTelemetry) {
19✔
428
            $defaultHeaders['X-Stripe-Client-Telemetry'] = self::_telemetryJson(self::$requestTelemetry);
×
429
        }
430

431
        $hasFile = false;
19✔
432
        foreach ($params as $k => $v) {
19✔
433
            if (\is_resource($v)) {
×
434
                $hasFile = true;
×
435
                $params[$k] = self::_processResourceParam($v);
×
436
            } elseif ($v instanceof \CURLFile) {
×
437
                $hasFile = true;
×
438
            }
439
        }
440

441
        if ($hasFile) {
19✔
442
            $defaultHeaders['Content-Type'] = 'multipart/form-data';
×
443
        } elseif ('preview' === $apiMode) {
19✔
444
            $defaultHeaders['Content-Type'] = 'application/json';
×
445
        } elseif ('standard' === $apiMode) {
19✔
446
            $defaultHeaders['Content-Type'] = 'application/x-www-form-urlencoded';
19✔
447
        } else {
448
            throw new Exception\InvalidArgumentException('Unknown API mode: ' . $apiMode);
×
449
        }
450

451
        $combinedHeaders = \array_merge($defaultHeaders, $headers);
19✔
452
        $rawHeaders = [];
19✔
453

454
        foreach ($combinedHeaders as $header => $value) {
19✔
455
            $rawHeaders[] = $header . ': ' . $value;
19✔
456
        }
457

458
        return [$absUrl, $rawHeaders, $params, $hasFile, $myApiKey];
19✔
459
    }
460

461
    /**
462
     * @param 'delete'|'get'|'post' $method
463
     * @param string $url
464
     * @param array $params
465
     * @param array $headers
466
     * @param 'preview'|'standard' $apiMode
467
     * @param string[] $usage
468
     *
469
     * @throws Exception\AuthenticationException
470
     * @throws Exception\ApiConnectionException
471
     *
472
     * @return array
473
     */
474
    private function _requestRaw($method, $url, $params, $headers, $apiMode, $usage)
20✔
475
    {
476
        list($absUrl, $rawHeaders, $params, $hasFile, $myApiKey) = $this->_prepareRequest($method, $url, $params, $headers, $apiMode);
20✔
477

478
        $requestStartMs = Util\Util::currentTimeMillis();
19✔
479

480
        list($rbody, $rcode, $rheaders) = $this->httpClient()->request(
19✔
481
            $method,
19✔
482
            $absUrl,
19✔
483
            $rawHeaders,
19✔
484
            $params,
19✔
485
            $hasFile,
19✔
486
            $apiMode
19✔
487
        );
19✔
488

489
        if (
490
            isset($rheaders['request-id'])
19✔
491
            && \is_string($rheaders['request-id'])
19✔
492
            && '' !== $rheaders['request-id']
19✔
493
        ) {
494
            self::$requestTelemetry = new RequestTelemetry(
×
495
                $rheaders['request-id'],
×
496
                Util\Util::currentTimeMillis() - $requestStartMs,
×
497
                $usage
×
498
            );
×
499
        }
500

501
        return [$rbody, $rcode, $rheaders, $myApiKey];
19✔
502
    }
503

504
    /**
505
     * @param 'delete'|'get'|'post' $method
506
     * @param string $url
507
     * @param array $params
508
     * @param array $headers
509
     * @param string[] $usage
510
     * @param callable $readBodyChunkCallable
511
     * @param 'preview'|'standard' $apiMode
512
     *
513
     * @throws Exception\AuthenticationException
514
     * @throws Exception\ApiConnectionException
515
     *
516
     * @return array
517
     */
518
    private function _requestRawStreaming($method, $url, $params, $headers, $apiMode, $usage, $readBodyChunkCallable)
×
519
    {
520
        list($absUrl, $rawHeaders, $params, $hasFile, $myApiKey) = $this->_prepareRequest($method, $url, $params, $headers, $apiMode);
×
521

522
        $requestStartMs = Util\Util::currentTimeMillis();
×
523

524
        list($rbody, $rcode, $rheaders) = $this->streamingHttpClient()->requestStream(
×
525
            $method,
×
526
            $absUrl,
×
527
            $rawHeaders,
×
528
            $params,
×
529
            $hasFile,
×
530
            $readBodyChunkCallable
×
531
        );
×
532

533
        if (
NEW
534
            isset($rheaders['request-id'])
×
NEW
535
            && \is_string($rheaders['request-id'])
×
NEW
536
            && '' !== $rheaders['request-id']
×
537
        ) {
538
            self::$requestTelemetry = new RequestTelemetry(
×
539
                $rheaders['request-id'],
×
540
                Util\Util::currentTimeMillis() - $requestStartMs
×
541
            );
×
542
        }
543

544
        return [$rbody, $rcode, $rheaders, $myApiKey];
×
545
    }
546

547
    /**
548
     * @param resource $resource
549
     *
550
     * @throws Exception\InvalidArgumentException
551
     *
552
     * @return \CURLFile|string
553
     */
554
    private function _processResourceParam($resource)
×
555
    {
556
        if ('stream' !== \get_resource_type($resource)) {
×
557
            throw new Exception\InvalidArgumentException(
×
558
                'Attempted to upload a resource that is not a stream'
×
559
            );
×
560
        }
561

562
        $metaData = \stream_get_meta_data($resource);
×
563
        if ('plainfile' !== $metaData['wrapper_type']) {
×
564
            throw new Exception\InvalidArgumentException(
×
565
                'Only plainfile resource streams are supported'
×
566
            );
×
567
        }
568

569
        // We don't have the filename or mimetype, but the API doesn't care
570
        return new \CURLFile($metaData['uri']);
×
571
    }
572

573
    /**
574
     * @param string $rbody
575
     * @param int    $rcode
576
     * @param array  $rheaders
577
     *
578
     * @throws Exception\UnexpectedValueException
579
     * @throws Exception\ApiErrorException
580
     *
581
     * @return array
582
     */
583
    private function _interpretResponse($rbody, $rcode, $rheaders)
19✔
584
    {
585
        $resp = \json_decode($rbody, true);
19✔
586
        $jsonError = \json_last_error();
19✔
587
        if (null === $resp && \JSON_ERROR_NONE !== $jsonError) {
19✔
588
            $msg = "Invalid response body from API: {$rbody} "
×
NEW
589
                . "(HTTP response code was {$rcode}, json_last_error() was {$jsonError})";
×
590

591
            throw new Exception\UnexpectedValueException($msg, $rcode);
×
592
        }
593

594
        if ($rcode < 200 || $rcode >= 300) {
19✔
595
            $this->handleErrorResponse($rbody, $rcode, $rheaders, $resp);
15✔
596
        }
597

598
        return $resp;
4✔
599
    }
600

601
    /**
602
     * @static
603
     *
604
     * @param HttpClient\ClientInterface $client
605
     */
606
    public static function setHttpClient($client)
24✔
607
    {
608
        self::$_httpClient = $client;
24✔
609
    }
610

611
    /**
612
     * @static
613
     *
614
     * @param HttpClient\StreamingClientInterface $client
615
     */
616
    public static function setStreamingHttpClient($client)
24✔
617
    {
618
        self::$_streamingHttpClient = $client;
24✔
619
    }
620

621
    /**
622
     * @static
623
     *
624
     * Resets any stateful telemetry data
625
     */
626
    public static function resetTelemetry()
×
627
    {
628
        self::$requestTelemetry = null;
×
629
    }
630

631
    /**
632
     * @return HttpClient\ClientInterface
633
     */
634
    private function httpClient()
20✔
635
    {
636
        if (!self::$_httpClient) {
20✔
637
            self::$_httpClient = HttpClient\CurlClient::instance();
×
638
        }
639

640
        return self::$_httpClient;
20✔
641
    }
642

643
    /**
644
     * @return HttpClient\StreamingClientInterface
645
     */
646
    private function streamingHttpClient()
×
647
    {
648
        if (!self::$_streamingHttpClient) {
×
649
            self::$_streamingHttpClient = HttpClient\CurlClient::instance();
×
650
        }
651

652
        return self::$_streamingHttpClient;
×
653
    }
654
}
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