Completed
Push — master ( c4fd35...06cc2b )
by Elf
01:24
created

HttpClient::getContent()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 2
cp 0
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
crap 2
1
<?php
2
3
namespace ElfSundae;
4
5
use Closure;
6
use Exception;
7
use GuzzleHttp\Client;
8
use Illuminate\Support\Arr;
9
use Illuminate\Support\Str;
10
use InvalidArgumentException;
11
use Psr\Http\Message\UriInterface;
12
13
class HttpClient
14
{
15
    /**
16
     * The default request options.
17
     *
18
     * @var array
19
     */
20
    protected static $defaultOptions = [
21
        'connect_timeout' => 5,
22
        'timeout' => 25,
23
    ];
24
25
    /**
26
     * The Guzzle client.
27
     *
28
     * @var \GuzzleHttp\Client
29
     */
30
    protected $client;
31
32
    /**
33
     * The request options.
34
     *
35
     * @var array
36
     */
37
    protected $options = [];
38
39
    /**
40
     * The Guzzle response.
41
     *
42
     * @var \GuzzleHttp\Psr7\Response
43
     */
44
    protected $response;
45
46
    /**
47
     * Indicate whether to catch Guzzle exceptions.
48
     *
49
     * @var bool
50
     */
51
    protected $catchExceptions = true;
52
53
    /**
54
     * Get the default request options.
55
     *
56
     * @return array
57
     */
58 24
    public static function defaultOptions()
59
    {
60 24
        return static::$defaultOptions;
61
    }
62
63
    /**
64
     * Set the default request options.
65
     *
66
     * @param  array  $options
67
     * @return void
68
     */
69 1
    public static function setDefaultOptions(array $options)
70
    {
71 1
        static::$defaultOptions = $options;
72 1
    }
73
74
    /**
75
     * Create a http client instance.
76
     *
77
     * @param  array|string|\Psr\Http\Message\UriInterface  $options  base URI or any request options
78
     *
79
     * @throws \InvalidArgumentException
80
     */
81 23
    public function __construct($options = [])
82
    {
83 23
        if (is_string($options) || $options instanceof UriInterface) {
84 1
            $options = ['base_uri' => $options];
85 23
        } elseif (! is_array($options)) {
86
            throw new InvalidArgumentException('Options must be a string, UriInterface, or an array');
87
        }
88
89 23
        $this->client = new Client(
90 23
            array_replace_recursive(static::defaultOptions(), $options)
91 23
        );
92
93 23
        $this->options = $this->client->getConfig();
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->client->getConfig() of type * is incompatible with the declared type array of property $options.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
94 23
    }
95
96
    /**
97
     * Get the Guzzle client instance.
98
     *
99
     * @return \GuzzleHttp\Client
100
     */
101 2
    public function getClient()
102
    {
103 2
        return $this->client;
104
    }
105
106
    /**
107
     * Get whether to catch Guzzle exceptions or not.
108
     *
109
     * @return bool
110
     */
111
    public function areExceptionsCaught()
112
    {
113
        return $this->catchExceptions;
114
    }
115
116
    /**
117
     * Set whether to catch Guzzle exceptions or not.
118
     *
119
     * @param  bool  $catch
120
     * @return $this
121
     */
122
    public function catchExceptions($catch)
123
    {
124
        $this->catchExceptions = (bool) $catch;
125
126
        return $this;
127
    }
128
129
    /**
130
     * Get the request options.
131
     *
132
     * @return array
133
     */
134 3
    public function getOptions()
135
    {
136 3
        return $this->options;
137
    }
138
139
    /**
140
     * Merge the given options to the request options.
141
     *
142
     * @param  array  $options,...
0 ignored issues
show
Documentation introduced by
There is no parameter named $options,.... Did you maybe mean $options?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
143
     * @return $this
144
     */
145
    public function mergeOptions(array ...$options)
146
    {
147
        $this->options = array_replace_recursive($this->options, ...$options);
148
149
        return $this;
150
    }
151
152
    /**
153
     * Remove one or many request options using "dot" notation.
154
     *
155
     * @param  array|string  $keys
156
     * @return $this
157
     */
158
    public function removeOptions($keys)
159
    {
160
        Arr::forget($this->options, is_array($keys) ? $keys : func_get_args());
161
162
        return $this;
163
    }
164
165
    /**
166
     * Get a request option using "dot" notation.
167
     *
168
     * @param  string  $key
169
     * @return mixed
170
     */
171 1
    public function getOption($key)
172
    {
173 1
        return Arr::get($this->options, $key);
174
    }
175
176
    /**
177
     * Set a request option using "dot" notation.
178
     *
179
     * @param  string|array  $key
180
     * @param  mixed  $value
181
     * @return $this
182
     */
183 1
    public function option($key, $value = null)
184
    {
185 1
        $keys = is_array($key) ? $key : [$key => $value];
186
187 1
        foreach ($keys as $key => $value) {
188 1
            Arr::set($this->options, $key, $value);
189 1
        }
190
191 1
        return $this;
192
    }
193
194
    /**
195
     * Set a request header.
196
     *
197
     * @param  string  $name
198
     * @param  mixed  $value
199
     * @return $this
200
     */
201
    public function header($name, $value)
202
    {
203
        return $this->option('headers.'.$name, $value);
204
    }
205
206
    /**
207
     * Set the request content type.
208
     *
209
     * @param  string  $type
210
     * @return $this
211
     */
212
    public function contentType($type)
213
    {
214
        return $this->header('Content-Type', $type);
215
    }
216
217
    /**
218
     * Set the request accept type.
219
     *
220
     * @param  string  $type
221
     * @return $this
222
     */
223
    public function accept($type)
224
    {
225
        return $this->header('Accept', $type);
226
    }
227
228
    /**
229
     * Set the request accept type to "application/json".
230
     *
231
     * @return $this
232
     */
233
    public function acceptJson()
234
    {
235
        return $this->accept('application/json');
236
    }
237
238
    /**
239
     * Specify where the body of the response will be saved.
240
     * Set the "sink" option.
241
     *
242
     * @param  mixed  $dest
243
     * @return $this
244
     */
245
    public function saveTo($dest)
246
    {
247
        return $this->removeOptions('save_to')->option('sink', $dest);
248
    }
249
250
    /**
251
     * Get the Guzzle response instance.
252
     *
253
     * @return \GuzzleHttp\Psr7\Response|null
254
     */
255
    public function getResponse()
256
    {
257
        return $this->response;
258
    }
259
260
    /**
261
     * Get data from the response.
262
     *
263
     * @param  string|\Closure  $callback
264
     * @param  array  $parameters
265
     * @param  mixed  $default
266
     * @return mixed
267
     */
268
    protected function getResponseData($callback, array $parameters = [], $default = null)
269
    {
270
        if ($this->response) {
271
            return $callback instanceof Closure
272
                ? $callback($this->response, ...$parameters)
273
                : $this->response->$callback(...$parameters);
274
        }
275
276
        return $default;
277
    }
278
279
    /**
280
     * Get the response content.
281
     *
282
     * @return string
283
     */
284
    public function getContent()
285
    {
286
        return (string) $this->getBody();
0 ignored issues
show
Documentation Bug introduced by
The method getBody does not exist on object<ElfSundae\HttpClient>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
287
    }
288
289
    /**
290
     * Get the JSON-decoded response content.
291
     *
292
     * @param  bool  $assoc
293
     * @return mixed
294
     */
295
    public function json($assoc = true)
296
    {
297
        return json_decode($this->getContent(), $assoc);
298
    }
299
300
    /**
301
     * Make request to a URI.
302
     *
303
     * @param  string  $uri
304
     * @param  string  $method
305
     * @param  array  $options
306
     * @return $this
307
     */
308 2
    public function request($uri = '', $method = 'GET', array $options = [])
309
    {
310 2
        $options = array_replace_recursive($this->options, $options);
311
312 2
        $this->response = null;
313
314
        try {
315 2
            $this->response = $this->client->request($method, $uri, $options);
316 2
        } catch (Exception $e) {
317 2
            if (! $this->catchExceptions) {
318
                throw $e;
319
            }
320
        }
321
322 2
        return $this;
323
    }
324
325
    /**
326
     * Make request to a URI, expecting JSON content.
327
     *
328
     * @param  string  $uri
329
     * @param  string  $method
330
     * @param  array  $options
331
     * @return $this
332
     */
333
    public function requestJson($uri = '', $method = 'GET', array $options = [])
334
    {
335
        $options = $this->addAcceptableJsonType(
336
            array_replace_recursive($this->options, $options)
337
        );
338
339
        return $this->request($uri, $method, $options);
340
    }
341
342
    /**
343
     * Add JSON type to the "Accept" header for the request options.
344
     *
345
     * @param  array  $options
346
     * @return array
347
     */
348
    protected function addAcceptableJsonType(array $options)
349
    {
350
        $accept = Arr::get($options, 'headers.Accept', '');
351
352
        if (! Str::contains($accept, ['/json', '+json'])) {
353
            $accept = rtrim('application/json,'.$accept, ',');
354
            Arr::set($options, 'headers.Accept', $accept);
355
        }
356
357
        return $options;
358
    }
359
360
    /**
361
     * Request the URI and return the response content.
362
     *
363
     * @param  string  $uri
364
     * @param  string  $method
365
     * @param  array  $options
366
     * @return string
367
     */
368
    public function fetchContent($uri = '', $method = 'GET', array $options = [])
369
    {
370
        return $this->request($uri, $method, $options)->getContent();
371
    }
372
373
    /**
374
     * Request the URI and return the JSON-decoded response content.
375
     *
376
     * @param  string  $uri
377
     * @param  string  $method
378
     * @param  array  $options
379
     * @return mixed
380
     */
381
    public function fetchJson($uri = '', $method = 'GET', array $options = [])
382
    {
383
        return $this->requestJson($uri, $method, $options)->json();
384
    }
385
386
    /**
387
     * Get the dynamic request methods.
388
     *
389
     * @return array
390
     */
391 2
    protected function getDynamicRequestMethods()
392
    {
393
        return [
394 2
            'get', 'head', 'put', 'post', 'patch', 'delete', 'options',
395 2
        ];
396
    }
397
398
    /**
399
     * Get the dynamic requestJson methods.
400
     *
401
     * @return array
402
     */
403 1
    protected function getDynamicRequestJsonMethods()
404
    {
405
        return [
406 1
            'get', 'put', 'post', 'patch', 'delete',
407 1
        ];
408
    }
409
410
    /**
411
     * Get the dynamic response methods.
412
     *
413
     * @return array
414
     */
415 1
    protected function getDynamicResponseMethods()
416
    {
417
        return [
418 1
            'getStatusCode', 'getReasonPhrase', 'getProtocolVersion',
419 1
            'getHeaders', 'hasHeader', 'getHeader', 'getHeaderLine', 'getBody',
420 1
        ];
421
    }
422
423
    /**
424
     * Insert HTTP method to the parameters.
425
     *
426
     * @param  array  $parameters
427
     * @return array
428
     */
429 1
    protected function insertHttpMethodToParameters($method, array $parameters)
430
    {
431 1
        $method = strtoupper($method);
432
433 1
        if (empty($parameters)) {
434
            $parameters = ['', $method];
435
        } else {
436 1
            array_splice($parameters, 1, 0, $method);
437
        }
438
439 1
        return $parameters;
440
    }
441
442
    /**
443
     * Dynamically send request, get response data, or set request option.
444
     *
445
     * @param  string  $method
446
     * @param  array  $parameters
447
     * @return mixed
448
     */
449 2
    public function __call($method, $parameters)
450
    {
451 2
        if (in_array($method, $this->getDynamicRequestMethods())) {
452 1
            return $this->request(
453 1
                ...$this->insertHttpMethodToParameters($method, $parameters)
0 ignored issues
show
Documentation introduced by
$this->insertHttpMethodT...s($method, $parameters) is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
454 1
            );
455
        }
456
457 1
        if (in_array($method, $this->getDynamicRequestJsonMethods())) {
458
            return $this->requestJson(
459
                ...$this->insertHttpMethodToParameters($method, $parameters)
0 ignored issues
show
Documentation introduced by
$this->insertHttpMethodT...s($method, $parameters) is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
460
            );
461
        }
462
463 1
        if (in_array($method, $this->getDynamicResponseMethods())) {
464
            return $this->getResponseData($method, $parameters);
465
        }
466
467 1
        return $this->option(Str::snake($method), ...$parameters);
468
    }
469
}
470