Completed
Push — master ( bbe280...709a47 )
by Elf
11:23 queued 01:26
created

HttpClient::contentType()   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 1
cp 0
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
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' => 30,
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 using "dot" notation.
131
     *
132
     * @param  string|null  $key
133
     * @return mixed
134 3
     */
135
    public function getOption($key = null)
136 3
    {
137
        return Arr::get($this->options, $key);
138
    }
139
140
    /**
141
     * Set the request options using "dot" notation.
142
     *
143
     * @param  string|array  $key
144
     * @param  mixed  $value
145
     * @return $this
146
     */
147
    public function option($key, $value = null)
148
    {
149
        $keys = is_array($key) ? $key : [$key => $value];
150
151
        foreach ($keys as $key => $value) {
152
            Arr::set($this->options, $key, $value);
153
        }
154
155
        return $this;
156
    }
157
158
    /**
159
     * Merge the given options to the request options.
160
     *
161
     * @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...
162
     * @return $this
163
     */
164
    public function mergeOption(array ...$options)
165
    {
166
        $this->options = array_replace_recursive($this->options, ...$options);
167
168
        return $this;
169
    }
170
171 1
    /**
172
     * Remove one or many request options using "dot" notation.
173 1
     *
174
     * @param  array|string  $keys
175
     * @return $this
176
     */
177
    public function removeOption($keys)
178
    {
179
        Arr::forget($this->options, is_array($keys) ? $keys : func_get_args());
180
181
        return $this;
182
    }
183 1
184
    /**
185 1
     * Set a request header.
186
     *
187 1
     * @param  string  $name
188 1
     * @param  mixed  $value
189 1
     * @return $this
190
     */
191 1
    public function header($name, $value)
192
    {
193
        return $this->option('headers.'.$name, $value);
194
    }
195
196
    /**
197
     * Set the request content type.
198
     *
199
     * @param  string  $type
200
     * @return $this
201
     */
202
    public function contentType($type)
203
    {
204
        return $this->header('Content-Type', $type);
205
    }
206
207
    /**
208
     * Set the request accept type.
209
     *
210
     * @param  string  $type
211
     * @return $this
212
     */
213
    public function accept($type)
214
    {
215
        return $this->header('Accept', $type);
216
    }
217
218
    /**
219
     * Set the request accept type to "application/json".
220
     *
221
     * @return $this
222
     */
223
    public function acceptJson()
224
    {
225
        return $this->accept('application/json');
226
    }
227
228
    /**
229
     * Specify where the body of the response will be saved.
230
     * Set the "sink" option.
231
     *
232
     * @param  mixed  $dest
233
     * @return $this
234
     */
235
    public function saveTo($dest)
236
    {
237
        return $this->removeOptions('save_to')->option('sink', $dest);
0 ignored issues
show
Bug introduced by
The method removeOptions() does not exist on ElfSundae\HttpClient. Did you maybe mean option()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
238
    }
239
240
    /**
241
     * Get the Guzzle response instance.
242
     *
243
     * @return \GuzzleHttp\Psr7\Response|null
244
     */
245
    public function getResponse()
246
    {
247
        return $this->response;
248
    }
249
250
    /**
251
     * Get data from the response.
252
     *
253
     * @param  string|\Closure  $callback
254
     * @param  array  $parameters
255
     * @param  mixed  $default
256
     * @return mixed
257
     */
258
    protected function getResponseData($callback, array $parameters = [], $default = null)
259
    {
260
        if ($this->response) {
261
            return $callback instanceof Closure
262
                ? $callback($this->response, ...$parameters)
263
                : $this->response->$callback(...$parameters);
264
        }
265
266
        return $default;
267
    }
268
269
    /**
270
     * Get the response content.
271
     *
272
     * @return string
273
     */
274
    public function getContent()
275
    {
276
        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...
277
    }
278
279
    /**
280
     * Get the JSON-decoded response content.
281
     *
282
     * @param  bool  $assoc
283
     * @return mixed
284
     */
285
    public function json($assoc = true)
286
    {
287
        return json_decode($this->getContent(), $assoc);
288
    }
289
290
    /**
291
     * Make request to a URI.
292
     *
293
     * @param  string  $uri
294
     * @param  string  $method
295
     * @param  array  $options
296
     * @return $this
297
     */
298
    public function request($uri = '', $method = 'GET', array $options = [])
299
    {
300
        $options = array_replace_recursive($this->options, $options);
301
302
        $this->response = null;
303
304
        try {
305
            $this->response = $this->client->request($method, $uri, $options);
306
        } catch (Exception $e) {
307
            if (! $this->catchExceptions) {
308 2
                throw $e;
309
            }
310 2
        }
311
312 2
        return $this;
313
    }
314
315 2
    /**
316 2
     * Make request to a URI, expecting JSON content.
317 2
     *
318
     * @param  string  $uri
319
     * @param  string  $method
320
     * @param  array  $options
321
     * @return $this
322 2
     */
323
    public function requestJson($uri = '', $method = 'GET', array $options = [])
324
    {
325
        $options = $this->addAcceptableJsonType(
326
            array_replace_recursive($this->options, $options)
327
        );
328
329
        return $this->request($uri, $method, $options);
330
    }
331
332
    /**
333
     * Add JSON type to the "Accept" header for the request options.
334
     *
335
     * @param  array  $options
336
     * @return array
337
     */
338
    protected function addAcceptableJsonType(array $options)
339
    {
340
        $accept = Arr::get($options, 'headers.Accept', '');
341
342
        if (! Str::contains($accept, ['/json', '+json'])) {
343
            $accept = rtrim('application/json,'.$accept, ',');
344
            Arr::set($options, 'headers.Accept', $accept);
345
        }
346
347
        return $options;
348
    }
349
350
    /**
351
     * Request the URI and return the response content.
352
     *
353
     * @param  string  $uri
354
     * @param  string  $method
355
     * @param  array  $options
356
     * @return string
357
     */
358
    public function fetchContent($uri = '', $method = 'GET', array $options = [])
359
    {
360
        return $this->request($uri, $method, $options)->getContent();
361
    }
362
363
    /**
364
     * Request the URI and return the JSON-decoded response content.
365
     *
366
     * @param  string  $uri
367
     * @param  string  $method
368
     * @param  array  $options
369
     * @return mixed
370
     */
371
    public function fetchJson($uri = '', $method = 'GET', array $options = [])
372
    {
373
        return $this->requestJson($uri, $method, $options)->json();
374
    }
375
376
    /**
377
     * Get the dynamic request methods.
378
     *
379
     * @return array
380
     */
381
    protected function getDynamicRequestMethods()
382
    {
383
        return [
384
            'get', 'head', 'put', 'post', 'patch', 'delete', 'options',
385
        ];
386
    }
387
388
    /**
389
     * Get the dynamic requestJson methods.
390
     *
391 2
     * @return array
392
     */
393
    protected function getDynamicRequestJsonMethods()
394 2
    {
395 2
        return [
396
            'get', 'put', 'post', 'patch', 'delete',
397
        ];
398
    }
399
400
    /**
401
     * Get the dynamic response methods.
402
     *
403 1
     * @return array
404
     */
405
    protected function getDynamicResponseMethods()
406 1
    {
407 1
        return [
408
            'getStatusCode', 'getReasonPhrase', 'getProtocolVersion',
409
            'getHeaders', 'hasHeader', 'getHeader', 'getHeaderLine', 'getBody',
410
        ];
411
    }
412
413
    /**
414
     * Insert HTTP method to the parameters.
415 1
     *
416
     * @param  array  $parameters
417
     * @return array
418 1
     */
419 1
    protected function insertHttpMethodToParameters($method, array $parameters)
420 1
    {
421
        $method = strtoupper($method);
422
423
        if (empty($parameters)) {
424
            $parameters = ['', $method];
425
        } else {
426
            array_splice($parameters, 1, 0, $method);
427
        }
428
429 1
        return $parameters;
430
    }
431 1
432
    /**
433 1
     * Dynamically send request, get response data, or set request option.
434
     *
435
     * @param  string  $method
436 1
     * @param  array  $parameters
437
     * @return mixed
438
     */
439 1
    public function __call($method, $parameters)
440
    {
441
        if (in_array($method, $this->getDynamicRequestMethods())) {
442
            return $this->request(
443
                ...$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...
444
            );
445
        }
446
447
        if (in_array($method, $this->getDynamicRequestJsonMethods())) {
448
            return $this->requestJson(
449 2
                ...$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...
450
            );
451 2
        }
452 1
453 1
        if (in_array($method, $this->getDynamicResponseMethods())) {
454 1
            return $this->getResponseData($method, $parameters);
455
        }
456
457 1
        return $this->option(Str::snake($method), ...$parameters);
458
    }
459
}
460