Completed
Push — master ( 49a639...f74bed )
by Elf
01:20
created

HttpClient::saveTo()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
crap 1
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 25
    public static function setDefaultOptions(array $options)
70
    {
71 25
        static::$defaultOptions = $options;
72 25
    }
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 24
    public function __construct($options = [])
82
    {
83 24
        if (is_string($options) || $options instanceof UriInterface) {
84 1
            $options = ['base_uri' => $options];
85 24
        } elseif (! is_array($options)) {
86 1
            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 4
    public function getClient()
102
    {
103 4
        return $this->client;
104
    }
105
106
    /**
107
     * Get whether to catch Guzzle exceptions or not.
108
     *
109
     * @return bool
110
     */
111 1
    public function areExceptionsCaught()
112
    {
113 1
        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 2
    public function catchExceptions($catch)
123
    {
124 2
        $this->catchExceptions = (bool) $catch;
125
126 2
        return $this;
127
    }
128
129
    /**
130
     * Get the request options using "dot" notation.
131
     *
132
     * @param  string|null  $key
133
     * @return mixed
134
     */
135 10
    public function getOption($key = null)
136
    {
137 10
        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 7
    public function option($key, $value = null)
148
    {
149 7
        $keys = is_array($key) ? $key : [$key => $value];
150
151 7
        foreach ($keys as $key => $value) {
152 7
            Arr::set($this->options, $key, $value);
153 7
        }
154
155 7
        return $this;
156
    }
157
158
    /**
159
     * Merge the given options to the request options.
160
     *
161
     * @param  array  ...$options
162
     * @return $this
163
     */
164 1
    public function mergeOption(array ...$options)
165
    {
166 1
        $this->options = array_replace_recursive($this->options, ...$options);
167
168 1
        return $this;
169
    }
170
171
    /**
172
     * Remove one or many request options using "dot" notation.
173
     *
174
     * @param  array|string  $keys
175
     * @return $this
176
     */
177 2
    public function removeOption($keys)
178
    {
179 2
        Arr::forget($this->options, is_array($keys) ? $keys : func_get_args());
180
181 2
        return $this;
182
    }
183
184
    /**
185
     * Set a request header.
186
     *
187
     * @param  string  $name
188
     * @param  mixed  $value
189
     * @return $this
190
     */
191 5
    public function header($name, $value)
192
    {
193 5
        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 1
    public function contentType($type)
203
    {
204 1
        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 3
    public function accept($type)
214
    {
215 3
        return $this->header('Accept', $type);
216
    }
217
218
    /**
219
     * Set the request accept type to "application/json".
220
     *
221
     * @return $this
222
     */
223 1
    public function acceptJson()
224
    {
225 1
        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 1
    public function saveTo($dest)
236
    {
237 1
        return $this->removeOption('save_to')->option('sink', $dest);
238
    }
239
240
    /**
241
     * Get the Guzzle response instance.
242
     *
243
     * @return \GuzzleHttp\Psr7\Response|null
244
     */
245 3
    public function getResponse()
246
    {
247 3
        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 5
    protected function getResponseData($callback, array $parameters = [], $default = null)
259
    {
260 5
        if ($this->response) {
261
            return $callback instanceof Closure
262 5
                ? $callback($this->response, ...$parameters)
263 5
                : $this->response->$callback(...$parameters);
264
        }
265
266
        return $default;
267
    }
268
269
    /**
270
     * Get the response content.
271
     *
272
     * @return string
273
     */
274 4
    public function getContent()
275
    {
276 4
        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 2
    public function getJsonContent($assoc = true)
286
    {
287 2
        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 10
    public function request($uri = '', $method = 'GET', array $options = [])
299
    {
300 10
        $this->response = null;
301
302 10
        $method = strtoupper($method);
303 10
        $options = array_replace_recursive($this->options, $options);
304
305
        try {
306 10
            $this->response = $this->client->request($method, $uri, $options);
307 10
        } catch (Exception $e) {
308 1
            if (! $this->catchExceptions) {
309 1
                throw $e;
310
            }
311
        }
312
313 10
        return $this;
314
    }
315
316
    /**
317
     * Make request to a URI, expecting JSON content.
318
     *
319
     * @param  string  $uri
320
     * @param  string  $method
321
     * @param  array  $options
322
     * @return $this
323
     */
324 4
    public function requestJson($uri = '', $method = 'GET', array $options = [])
325
    {
326 4
        $options = $this->addAcceptableJsonType(
327 4
            array_replace_recursive($this->options, $options)
328 4
        );
329
330 4
        return $this->request($uri, $method, $options);
331
    }
332
333
    /**
334
     * Add JSON type to the "Accept" header for the request options.
335
     *
336
     * @param  array  $options
337
     * @return array
338
     */
339 4
    protected function addAcceptableJsonType(array $options)
340
    {
341 4
        $accept = Arr::get($options, 'headers.Accept', '');
342
343 4
        if (! Str::contains($accept, ['/json', '+json'])) {
344 4
            $accept = rtrim('application/json,'.$accept, ',');
345 4
            Arr::set($options, 'headers.Accept', $accept);
346 4
        }
347
348 4
        return $options;
349
    }
350
351
    /**
352
     * Request the URI and return the response content.
353
     *
354
     * @param  string  $uri
355
     * @param  string  $method
356
     * @param  array  $options
357
     * @return string
358
     */
359 1
    public function fetchContent($uri = '', $method = 'GET', array $options = [])
360
    {
361 1
        return $this->request($uri, $method, $options)->getContent();
362
    }
363
364
    /**
365
     * Request the URI and return the JSON-decoded response content.
366
     *
367
     * @param  string  $uri
368
     * @param  string  $method
369
     * @param  array  $options
370
     * @return mixed
371
     */
372 1
    public function fetchJson($uri = '', $method = 'GET', array $options = [])
373
    {
374 1
        return $this->requestJson($uri, $method, $options)->getJsonContent();
375
    }
376
377
    /**
378
     * Get all allowed magic request methods.
379
     *
380
     * @return array
381
     */
382 7
    protected function getMagicRequestMethods()
383
    {
384
        return [
385 7
            'get', 'head', 'put', 'post', 'patch', 'delete', 'options',
386 7
        ];
387
    }
388
389
    /**
390
     * Get all allowed magic response methods.
391
     *
392
     * @return array
393
     */
394 5
    protected function getMagicResponseMethods()
395
    {
396
        return [
397 5
            'getStatusCode', 'getReasonPhrase', 'getProtocolVersion',
398 5
            'getHeaders', 'hasHeader', 'getHeader', 'getHeaderLine', 'getBody',
399 5
        ];
400
    }
401
402
    /**
403
     * Determine if the given method is a magic request method.
404
     *
405
     * @param  string  $method
406
     * @param  string  &$requestMethod
407
     * @param  string  &$httpMethod
408
     * @return bool
409
     */
410 7
    protected function isMagicRequest($method, &$requestMethod, &$httpMethod)
411
    {
412 7
        if (strlen($method) > 4 && $pos = strrpos($method, 'Json', -4)) {
413 2
            $httpMethod = substr($method, 0, $pos);
414 2
            $requestMethod = 'requestJson';
415 2
        } else {
416 7
            $httpMethod = $method;
417 7
            $requestMethod = 'request';
418
        }
419
420 7
        if (in_array($httpMethod, $this->getMagicRequestMethods())) {
421 2
            return true;
422
        }
423
424 5
        $httpMethod = $requestMethod = null;
425
426 5
        return false;
427
    }
428
429
    /**
430
     * Get parameters for $this->request() from the magic request methods.
431
     *
432
     * @param  string  $httpMethod
433
     * @param  array  $parameters
434
     * @return array
435
     */
436 2
    protected function getRequestParameters($httpMethod, array $parameters)
437
    {
438 2
        if (empty($parameters)) {
439 1
            $parameters = ['', $httpMethod];
440 1
        } else {
441 2
            array_splice($parameters, 1, 0, $httpMethod);
442
        }
443
444 2
        return $parameters;
445
    }
446
447
    /**
448
     * Handle magic method to send request, get response data, or set
449
     * request options.
450
     *
451
     * @param  string  $method
452
     * @param  array  $parameters
453
     * @return mixed
454
     */
455 7
    public function __call($method, $parameters)
456
    {
457 7
        if ($this->isMagicRequest($method, $request, $httpMethod)) {
458 2
            return $this->{$request}(
459 2
                ...$this->getRequestParameters($httpMethod, $parameters)
460 2
            );
461
        }
462
463 5
        if (in_array($method, $this->getMagicResponseMethods())) {
464 5
            return $this->getResponseData($method, $parameters);
465
        }
466
467
        return $this->option(Str::snake($method), ...$parameters);
468
    }
469
}
470