Client   F
last analyzed

Complexity

Total Complexity 72

Size/Duplication

Total Lines 388
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 167
c 0
b 0
f 0
dl 0
loc 388
rs 2.64
wmc 72

13 Methods

Rating   Name   Duplication   Size   Complexity  
F applyOptions() 0 113 26
A __construct() 0 14 4
A request() 0 4 1
A invalidBody() 0 7 1
A transfer() 0 21 4
A requestAsync() 0 17 5
A send() 0 4 1
B configureDefaults() 0 45 10
A getConfig() 0 5 3
A sendAsync() 0 8 1
B prepareDefaults() 0 33 7
A buildUri() 0 10 5
A __call() 0 12 4

How to fix   Complexity   

Complex Class

Complex classes like Client often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Client, and based on these observations, apply Extract Interface, too.

1
<?php
2
namespace GuzzleHttp;
3
4
use GuzzleHttp\Cookie\CookieJar;
5
use GuzzleHttp\Promise;
6
use GuzzleHttp\Psr7;
7
use Psr\Http\Message\UriInterface;
8
use Psr\Http\Message\RequestInterface;
9
use Psr\Http\Message\ResponseInterface;
10
11
/**
12
 * @method ResponseInterface get(string|UriInterface $uri, array $options = [])
13
 * @method ResponseInterface head(string|UriInterface $uri, array $options = [])
14
 * @method ResponseInterface put(string|UriInterface $uri, array $options = [])
15
 * @method ResponseInterface post(string|UriInterface $uri, array $options = [])
16
 * @method ResponseInterface patch(string|UriInterface $uri, array $options = [])
17
 * @method ResponseInterface delete(string|UriInterface $uri, array $options = [])
18
 * @method Promise\PromiseInterface getAsync(string|UriInterface $uri, array $options = [])
19
 * @method Promise\PromiseInterface headAsync(string|UriInterface $uri, array $options = [])
20
 * @method Promise\PromiseInterface putAsync(string|UriInterface $uri, array $options = [])
21
 * @method Promise\PromiseInterface postAsync(string|UriInterface $uri, array $options = [])
22
 * @method Promise\PromiseInterface patchAsync(string|UriInterface $uri, array $options = [])
23
 * @method Promise\PromiseInterface deleteAsync(string|UriInterface $uri, array $options = [])
24
 */
25
class Client implements ClientInterface
26
{
27
    /** @var array Default request options */
28
    private $config;
29
30
    /**
31
     * Clients accept an array of constructor parameters.
32
     *
33
     * Here's an example of creating a client using a base_uri and an array of
34
     * default request options to apply to each request:
35
     *
36
     *     $client = new Client([
37
     *         'base_uri'        => 'http://www.foo.com/1.0/',
38
     *         'timeout'         => 0,
39
     *         'allow_redirects' => false,
40
     *         'proxy'           => '192.168.16.1:10'
41
     *     ]);
42
     *
43
     * Client configuration settings include the following options:
44
     *
45
     * - handler: (callable) Function that transfers HTTP requests over the
46
     *   wire. The function is called with a Psr7\Http\Message\RequestInterface
47
     *   and array of transfer options, and must return a
48
     *   GuzzleHttp\Promise\PromiseInterface that is fulfilled with a
49
     *   Psr7\Http\Message\ResponseInterface on success. "handler" is a
50
     *   constructor only option that cannot be overridden in per/request
51
     *   options. If no handler is provided, a default handler will be created
52
     *   that enables all of the request options below by attaching all of the
53
     *   default middleware to the handler.
54
     * - base_uri: (string|UriInterface) Base URI of the client that is merged
55
     *   into relative URIs. Can be a string or instance of UriInterface.
56
     * - **: any request option
57
     *
58
     * @param array $config Client configuration settings.
59
     *
60
     * @see \GuzzleHttp\RequestOptions for a list of available request options.
61
     */
62
    public function __construct(array $config = [])
63
    {
64
        if (!isset($config['handler'])) {
65
            $config['handler'] = HandlerStack::create();
66
        } elseif (!is_callable($config['handler'])) {
67
            throw new \InvalidArgumentException('handler must be a callable');
68
        }
69
70
        // Convert the base_uri to a UriInterface
71
        if (isset($config['base_uri'])) {
72
            $config['base_uri'] = Psr7\uri_for($config['base_uri']);
0 ignored issues
show
Deprecated Code introduced by
The function GuzzleHttp\Psr7\uri_for() has been deprecated: uri_for will be removed in guzzlehttp/psr7:2.0. Use Utils::uriFor instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

72
            $config['base_uri'] = /** @scrutinizer ignore-deprecated */ Psr7\uri_for($config['base_uri']);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
73
        }
74
75
        $this->configureDefaults($config);
76
    }
77
78
    public function __call($method, $args)
79
    {
80
        if (count($args) < 1) {
81
            throw new \InvalidArgumentException('Magic request methods require a URI and optional options array');
82
        }
83
84
        $uri = $args[0];
85
        $opts = isset($args[1]) ? $args[1] : [];
86
87
        return substr($method, -5) === 'Async'
88
            ? $this->requestAsync(substr($method, 0, -5), $uri, $opts)
89
            : $this->request($method, $uri, $opts);
90
    }
91
92
    public function sendAsync(RequestInterface $request, array $options = [])
93
    {
94
        // Merge the base URI into the request URI if needed.
95
        $options = $this->prepareDefaults($options);
96
97
        return $this->transfer(
98
            $request->withUri($this->buildUri($request->getUri(), $options), $request->hasHeader('Host')),
99
            $options
100
        );
101
    }
102
103
    public function send(RequestInterface $request, array $options = [])
104
    {
105
        $options[RequestOptions::SYNCHRONOUS] = true;
106
        return $this->sendAsync($request, $options)->wait();
107
    }
108
109
    public function requestAsync($method, $uri = '', array $options = [])
110
    {
111
        $options = $this->prepareDefaults($options);
112
        // Remove request modifying parameter because it can be done up-front.
113
        $headers = isset($options['headers']) ? $options['headers'] : [];
114
        $body = isset($options['body']) ? $options['body'] : null;
115
        $version = isset($options['version']) ? $options['version'] : '1.1';
116
        // Merge the URI into the base URI.
117
        $uri = $this->buildUri($uri, $options);
118
        if (is_array($body)) {
119
            $this->invalidBody();
120
        }
121
        $request = new Psr7\Request($method, $uri, $headers, $body, $version);
122
        // Remove the option so that they are not doubly-applied.
123
        unset($options['headers'], $options['body'], $options['version']);
124
125
        return $this->transfer($request, $options);
126
    }
127
128
    public function request($method, $uri = '', array $options = [])
129
    {
130
        $options[RequestOptions::SYNCHRONOUS] = true;
131
        return $this->requestAsync($method, $uri, $options)->wait();
132
    }
133
134
    public function getConfig($option = null)
135
    {
136
        return $option === null
137
            ? $this->config
138
            : (isset($this->config[$option]) ? $this->config[$option] : null);
139
    }
140
141
    private function buildUri($uri, array $config)
142
    {
143
        // for BC we accept null which would otherwise fail in uri_for
144
        $uri = Psr7\uri_for($uri === null ? '' : $uri);
0 ignored issues
show
Deprecated Code introduced by
The function GuzzleHttp\Psr7\uri_for() has been deprecated: uri_for will be removed in guzzlehttp/psr7:2.0. Use Utils::uriFor instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

144
        $uri = /** @scrutinizer ignore-deprecated */ Psr7\uri_for($uri === null ? '' : $uri);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
145
146
        if (isset($config['base_uri'])) {
147
            $uri = Psr7\UriResolver::resolve(Psr7\uri_for($config['base_uri']), $uri);
0 ignored issues
show
Deprecated Code introduced by
The function GuzzleHttp\Psr7\uri_for() has been deprecated: uri_for will be removed in guzzlehttp/psr7:2.0. Use Utils::uriFor instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

147
            $uri = Psr7\UriResolver::resolve(/** @scrutinizer ignore-deprecated */ Psr7\uri_for($config['base_uri']), $uri);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
148
        }
149
150
        return $uri->getScheme() === '' && $uri->getHost() !== '' ? $uri->withScheme('http') : $uri;
151
    }
152
153
    /**
154
     * Configures the default options for a client.
155
     *
156
     * @param array $config
157
     */
158
    private function configureDefaults(array $config)
159
    {
160
        $defaults = [
161
            'allow_redirects' => RedirectMiddleware::$defaultSettings,
162
            'http_errors'     => true,
163
            'decode_content'  => true,
164
            'verify'          => true,
165
            'cookies'         => false
166
        ];
167
168
        // Use the standard Linux HTTP_PROXY and HTTPS_PROXY if set.
169
170
        // We can only trust the HTTP_PROXY environment variable in a CLI
171
        // process due to the fact that PHP has no reliable mechanism to
172
        // get environment variables that start with "HTTP_".
173
        if (php_sapi_name() == 'cli' && getenv('HTTP_PROXY')) {
174
            $defaults['proxy']['http'] = getenv('HTTP_PROXY');
175
        }
176
177
        if ($proxy = getenv('HTTPS_PROXY')) {
178
            $defaults['proxy']['https'] = $proxy;
179
        }
180
181
        if ($noProxy = getenv('NO_PROXY')) {
182
            $cleanedNoProxy = str_replace(' ', '', $noProxy);
183
            $defaults['proxy']['no'] = explode(',', $cleanedNoProxy);
184
        }
185
186
        $this->config = $config + $defaults;
187
188
        if (!empty($config['cookies']) && $config['cookies'] === true) {
189
            $this->config['cookies'] = new CookieJar();
190
        }
191
192
        // Add the default user-agent header.
193
        if (!isset($this->config['headers'])) {
194
            $this->config['headers'] = ['User-Agent' => default_user_agent()];
195
        } else {
196
            // Add the User-Agent header if one was not already set.
197
            foreach (array_keys($this->config['headers']) as $name) {
198
                if (strtolower($name) === 'user-agent') {
199
                    return;
200
                }
201
            }
202
            $this->config['headers']['User-Agent'] = default_user_agent();
203
        }
204
    }
205
206
    /**
207
     * Merges default options into the array.
208
     *
209
     * @param array $options Options to modify by reference
210
     *
211
     * @return array
212
     */
213
    private function prepareDefaults($options)
214
    {
215
        $defaults = $this->config;
216
217
        if (!empty($defaults['headers'])) {
218
            // Default headers are only added if they are not present.
219
            $defaults['_conditional'] = $defaults['headers'];
220
            unset($defaults['headers']);
221
        }
222
223
        // Special handling for headers is required as they are added as
224
        // conditional headers and as headers passed to a request ctor.
225
        if (array_key_exists('headers', $options)) {
226
            // Allows default headers to be unset.
227
            if ($options['headers'] === null) {
228
                $defaults['_conditional'] = null;
229
                unset($options['headers']);
230
            } elseif (!is_array($options['headers'])) {
231
                throw new \InvalidArgumentException('headers must be an array');
232
            }
233
        }
234
235
        // Shallow merge defaults underneath options.
236
        $result = $options + $defaults;
237
238
        // Remove null values.
239
        foreach ($result as $k => $v) {
240
            if ($v === null) {
241
                unset($result[$k]);
242
            }
243
        }
244
245
        return $result;
246
    }
247
248
    /**
249
     * Transfers the given request and applies request options.
250
     *
251
     * The URI of the request is not modified and the request options are used
252
     * as-is without merging in default options.
253
     *
254
     * @param RequestInterface $request
255
     * @param array            $options
256
     *
257
     * @return Promise\PromiseInterface
258
     */
259
    private function transfer(RequestInterface $request, array $options)
260
    {
261
        // save_to -> sink
262
        if (isset($options['save_to'])) {
263
            $options['sink'] = $options['save_to'];
264
            unset($options['save_to']);
265
        }
266
267
        // exceptions -> http_errors
268
        if (isset($options['exceptions'])) {
269
            $options['http_errors'] = $options['exceptions'];
270
            unset($options['exceptions']);
271
        }
272
273
        $request = $this->applyOptions($request, $options);
274
        $handler = $options['handler'];
275
276
        try {
277
            return Promise\promise_for($handler($request, $options));
0 ignored issues
show
Deprecated Code introduced by
The function GuzzleHttp\Promise\promise_for() has been deprecated: promise_for will be removed in guzzlehttp/promises:2.0. Use Create::promiseFor instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

277
            return /** @scrutinizer ignore-deprecated */ Promise\promise_for($handler($request, $options));

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
278
        } catch (\Exception $e) {
279
            return Promise\rejection_for($e);
0 ignored issues
show
Deprecated Code introduced by
The function GuzzleHttp\Promise\rejection_for() has been deprecated: rejection_for will be removed in guzzlehttp/promises:2.0. Use Create::rejectionFor instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

279
            return /** @scrutinizer ignore-deprecated */ Promise\rejection_for($e);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
280
        }
281
    }
282
283
    /**
284
     * Applies the array of request options to a request.
285
     *
286
     * @param RequestInterface $request
287
     * @param array            $options
288
     *
289
     * @return RequestInterface
290
     */
291
    private function applyOptions(RequestInterface $request, array &$options)
292
    {
293
        $modify = [];
294
295
        if (isset($options['form_params'])) {
296
            if (isset($options['multipart'])) {
297
                throw new \InvalidArgumentException('You cannot use '
298
                    . 'form_params and multipart at the same time. Use the '
299
                    . 'form_params option if you want to send application/'
300
                    . 'x-www-form-urlencoded requests, and the multipart '
301
                    . 'option to send multipart/form-data requests.');
302
            }
303
            $options['body'] = http_build_query($options['form_params'], '', '&');
304
            unset($options['form_params']);
305
            $options['_conditional']['Content-Type'] = 'application/x-www-form-urlencoded';
306
        }
307
308
        if (isset($options['multipart'])) {
309
            $options['body'] = new Psr7\MultipartStream($options['multipart']);
310
            unset($options['multipart']);
311
        }
312
313
        if (isset($options['json'])) {
314
            $options['body'] = \GuzzleHttp\json_encode($options['json']);
315
            unset($options['json']);
316
            $options['_conditional']['Content-Type'] = 'application/json';
317
        }
318
319
        if (!empty($options['decode_content'])
320
            && $options['decode_content'] !== true
321
        ) {
322
            $modify['set_headers']['Accept-Encoding'] = $options['decode_content'];
323
        }
324
325
        if (isset($options['headers'])) {
326
            if (isset($modify['set_headers'])) {
327
                $modify['set_headers'] = $options['headers'] + $modify['set_headers'];
328
            } else {
329
                $modify['set_headers'] = $options['headers'];
330
            }
331
            unset($options['headers']);
332
        }
333
334
        if (isset($options['body'])) {
335
            if (is_array($options['body'])) {
336
                $this->invalidBody();
337
            }
338
            $modify['body'] = Psr7\stream_for($options['body']);
0 ignored issues
show
Deprecated Code introduced by
The function GuzzleHttp\Psr7\stream_for() has been deprecated: stream_for will be removed in guzzlehttp/psr7:2.0. Use Utils::streamFor instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

338
            $modify['body'] = /** @scrutinizer ignore-deprecated */ Psr7\stream_for($options['body']);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
339
            unset($options['body']);
340
        }
341
342
        if (!empty($options['auth']) && is_array($options['auth'])) {
343
            $value = $options['auth'];
344
            $type = isset($value[2]) ? strtolower($value[2]) : 'basic';
345
            switch ($type) {
346
                case 'basic':
347
                    $modify['set_headers']['Authorization'] = 'Basic '
348
                        . base64_encode("$value[0]:$value[1]");
349
                    break;
350
                case 'digest':
351
                    // @todo: Do not rely on curl
352
                    $options['curl'][CURLOPT_HTTPAUTH] = CURLAUTH_DIGEST;
353
                    $options['curl'][CURLOPT_USERPWD] = "$value[0]:$value[1]";
354
                    break;
355
                case 'ntlm':
356
                    $options['curl'][CURLOPT_HTTPAUTH] = CURLAUTH_NTLM;
357
                    $options['curl'][CURLOPT_USERPWD] = "$value[0]:$value[1]";
358
                    break;
359
            }
360
        }
361
362
        if (isset($options['query'])) {
363
            $value = $options['query'];
364
            if (is_array($value)) {
365
                $value = http_build_query($value, null, '&', PHP_QUERY_RFC3986);
0 ignored issues
show
Bug introduced by
null of type null is incompatible with the type string expected by parameter $numeric_prefix of http_build_query(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

365
                $value = http_build_query($value, /** @scrutinizer ignore-type */ null, '&', PHP_QUERY_RFC3986);
Loading history...
366
            }
367
            if (!is_string($value)) {
368
                throw new \InvalidArgumentException('query must be a string or array');
369
            }
370
            $modify['query'] = $value;
371
            unset($options['query']);
372
        }
373
374
        // Ensure that sink is not an invalid value.
375
        if (isset($options['sink'])) {
376
            // TODO: Add more sink validation?
377
            if (is_bool($options['sink'])) {
378
                throw new \InvalidArgumentException('sink must not be a boolean');
379
            }
380
        }
381
382
        $request = Psr7\modify_request($request, $modify);
0 ignored issues
show
Deprecated Code introduced by
The function GuzzleHttp\Psr7\modify_request() has been deprecated: modify_request will be removed in guzzlehttp/psr7:2.0. Use Utils::modifyRequest instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

382
        $request = /** @scrutinizer ignore-deprecated */ Psr7\modify_request($request, $modify);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
383
        if ($request->getBody() instanceof Psr7\MultipartStream) {
384
            // Use a multipart/form-data POST if a Content-Type is not set.
385
            $options['_conditional']['Content-Type'] = 'multipart/form-data; boundary='
386
                . $request->getBody()->getBoundary();
387
        }
388
389
        // Merge in conditional headers if they are not present.
390
        if (isset($options['_conditional'])) {
391
            // Build up the changes so it's in a single clone of the message.
392
            $modify = [];
393
            foreach ($options['_conditional'] as $k => $v) {
394
                if (!$request->hasHeader($k)) {
395
                    $modify['set_headers'][$k] = $v;
396
                }
397
            }
398
            $request = Psr7\modify_request($request, $modify);
0 ignored issues
show
Deprecated Code introduced by
The function GuzzleHttp\Psr7\modify_request() has been deprecated: modify_request will be removed in guzzlehttp/psr7:2.0. Use Utils::modifyRequest instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

398
            $request = /** @scrutinizer ignore-deprecated */ Psr7\modify_request($request, $modify);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
399
            // Don't pass this internal value along to middleware/handlers.
400
            unset($options['_conditional']);
401
        }
402
403
        return $request;
404
    }
405
406
    private function invalidBody()
407
    {
408
        throw new \InvalidArgumentException('Passing in the "body" request '
409
            . 'option as an array to send a POST request has been deprecated. '
410
            . 'Please use the "form_params" request option to send a '
411
            . 'application/x-www-form-urlencoded request, or the "multipart" '
412
            . 'request option to send a multipart/form-data request.');
413
    }
414
}
415