Completed
Push — master ( ad56b7...8e7a9c )
by Stefan
01:10
created

Options   C

Complexity

Total Complexity 54

Size/Duplication

Total Lines 527
Duplicated Lines 2.28 %

Coupling/Cohesion

Components 2
Dependencies 3

Importance

Changes 0
Metric Value
wmc 54
lcom 2
cbo 3
dl 12
loc 527
rs 6.4799
c 0
b 0
f 0

37 Methods

Rating   Name   Duplication   Size   Complexity  
A authBearer() 0 4 1
A authBasic() 0 10 2
A throwErrors() 0 6 1
A shouldThrowErrors() 0 4 1
A header() 0 6 1
A headers() 0 8 2
A baseUri() 0 4 1
A disallowRedirects() 0 4 1
A allowRedirects() 0 4 1
A timeout() 0 4 1
A useProxy() 0 4 1
A maxDuration() 0 4 1
A doNotVerifySsl() 0 7 1
A verifySsl() 0 7 1
A contentType() 0 4 1
A userAgent() 0 4 1
A accept() 0 4 1
A option() 0 6 1
A queryParam() 0 6 1
A queryParams() 0 8 2
A customData() 0 4 1
A bodyFormat() 0 6 1
A asPlainText() 0 6 1
A asJson() 0 6 1
A asFormParams() 0 6 1
A asMultipart() 0 6 1
A getHeaders() 0 4 1
A getContentType() 0 6 2
A getBodyFormat() 0 4 1
A payloadMustBeArray() 0 7 1
A throwExceptionWhenPayloadIsNotArray() 0 8 2
A payload() 0 4 1
A getOption() 0 4 1
B resolvePayload() 12 50 11
A removeOption() 0 6 1
A removeOptions() 0 12 3
A body() 0 4 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Options 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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 Options, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Gemz\HttpClient\Contracts;
4
5
use Gemz\HttpClient\Exceptions\InvalidArgument;
6
use Symfony\Component\Mime\Part\Multipart\FormDataPart;
7
8
trait Options
9
{
10
    /** @var string */
11
    private static $CONTENT_TYPE_JSON = 'application/json';
12
13
    /** @var string */
14
    private static $CONTENT_TYPE_PLAIN = 'text/plain';
15
16
    /** @var string */
17
    private static $CONTENT_TYPE_MULTIPART = 'multipart/form-data';
18
19
    /** @var string */
20
    private static $CONTENT_TYPE_FORM_PARAMS = 'application/x-www-form-urlencoded';
21
22
    /** @var string */
23
    protected static $CUSTOM_DATA_HEADER = 'X-Custom-Data';
24
25
    /** @var array<mixed> */
26
    protected $options = [];
27
28
    /** @var bool */
29
    protected $throwErrors = false;
30
31
    /** @var array<String> */
32
    protected $bodyFormats = ['json', 'multipart', 'form_params'];
33
34
    /** @var string */
35
    protected $bodyFormat = 'json';
36
37
    /**
38
     * Set authentication auth bearer token
39
     *
40
     * @param string $token
41
     *
42
     * @return $this
43
     */
44
    public function authBearer(string $token): self
45
    {
46
        return $this->option('auth_bearer', $token);
47
    }
48
49
    /**
50
     * Set authentication auth basic. If password is null
51
     * only username will be used
52
     *
53
     * @param string $username
54
     * @param string $password
55
     *
56
     * @return $this
57
     */
58
    public function authBasic(string $username, string $password = ''): self
59
    {
60
        $this->options['auth_basic'] = $username;
61
62
        if ('' !== $password) {
63
            $this->options['auth_basic'] .= ':' . $password;
64
        }
65
66
        return $this;
67
    }
68
69
    /**
70
     * @return $this
71
     */
72
    public function throwErrors(): self
73
    {
74
        $this->throwErrors = true;
75
76
        return $this;
77
    }
78
79
    /**
80
     * @return bool
81
     */
82
    public function shouldThrowErrors(): bool
83
    {
84
        return $this->throwErrors;
85
    }
86
87
    /**
88
     * Values for existing header keys will be replaced
89
     *
90
     * @param string $key
91
     * @param string $value
92
     *
93
     * @return $this
94
     */
95
    public function header(string $key, string $value): self
96
    {
97
        $this->options['headers'][$key] = $value;
98
99
        return $this;
100
    }
101
102
    /**
103
     * Values for existing header keys will be replaced
104
     *
105
     * @param array<String> $headers
106
     *
107
     * @return $this
108
     */
109
    public function headers(array $headers): self
110
    {
111
        foreach ($headers as $key => $value) {
112
            $this->header($key, $value);
113
        }
114
115
        return $this;
116
    }
117
118
    /**
119
     * Set the base uri.
120
     *
121
     * @param string $uri
122
     *
123
     * @return $this
124
     */
125
    public function baseUri(string $uri): self
126
    {
127
        return $this->option('base_uri', $uri);
128
    }
129
130
    /**
131
     * Disallows redirects.
132
     *
133
     * @return $this
134
     */
135
    public function disallowRedirects(): self
136
    {
137
        return $this->option('max_redirects', -1);
138
    }
139
140
    /**
141
     * @param int $max
142
     *
143
     * @return $this
144
     */
145
    public function allowRedirects(int $max = 0): self
146
    {
147
        return $this->option('max_redirects', $max);
148
    }
149
150
    /**
151
     * Float describing the timeout of the request in seconds.
152
     * use 0 to wait indefinitely
153
     *
154
     * @param float $seconds
155
     *
156
     * @return $this
157
     */
158
    public function timeout(float $seconds): self
159
    {
160
        return $this->option('timeout', $seconds);
161
    }
162
163
    /**
164
     * Pass a string to specify an HTTP proxy, or an array to specify different proxies for different protocols.
165
     *
166
     * @param string|array<String> $proxy
167
     *
168
     * @return $this
169
     */
170
    public function useProxy($proxy): self
171
    {
172
        return $this->option('proxy', $proxy);
173
    }
174
175
    /**
176
     * @param float $seconds
177
     *
178
     * @return $this
179
     */
180
    public function maxDuration(float $seconds): self
181
    {
182
        return $this->option('max_duration', $seconds);
183
    }
184
185
    /**
186
     * Does not verify SSL certificates
187
     *
188
     * @return $this
189
     */
190
    public function doNotVerifySsl(): self
191
    {
192
        $this->option('verify_peer', false);
193
        $this->option('verify_host', false);
194
195
        return $this;
196
    }
197
198
    /**
199
     * Does verify SSL certificates
200
     *
201
     * @return $this
202
     */
203
    public function verifySsl(): self
204
    {
205
        $this->option('verify_peer', true);
206
        $this->option('verify_host', true);
207
208
        return $this;
209
    }
210
211
    /**
212
     * Set the content type
213
     *
214
     * @param string $type
215
     *
216
     * @return $this
217
     */
218
    public function contentType(string $type): self
219
    {
220
        return $this->header('Content-Type', $type);
221
    }
222
223
    /**
224
     * Set the clients user agent
225
     *
226
     * @param string $agent
227
     *
228
     * @return $this
229
     */
230
    public function userAgent(string $agent): self
231
    {
232
        return $this->header('User-Agent', $agent);
233
    }
234
235
    /**
236
     * Set accept header
237
     *
238
     * @param string $value
239
     *
240
     * @return $this
241
     */
242
    public function accept(string $value): self
243
    {
244
        return $this->header('Accept', $value);
245
    }
246
247
    /**
248
     * Set option according guzzle request options
249
     *
250
     * @param string $key
251
     * @param mixed $value
252
     *
253
     * @return $this
254
     */
255
    public function option(string $key, $value): self
256
    {
257
        $this->options[$key] = $value;
258
259
        return $this;
260
    }
261
262
    /**
263
     * Set param for the query url
264
     *
265
     * @param string $key
266
     * @param string $value
267
     *
268
     * @return $this
269
     */
270
    public function queryParam(string $key, string $value): self
271
    {
272
        $this->options['query'][$key] = $value;
273
274
        return $this;
275
    }
276
277
    /**
278
     * Set multiple params for query url
279
     * in form of [<key> => <value>, <key2> => <value2>]
280
     *
281
     * @param array<String> $params
282
     *
283
     * @return $this
284
     */
285
    public function queryParams(array $params): self
286
    {
287
        foreach ($params as $key => $value) {
288
            $this->queryParam($key, $value);
289
        }
290
291
        return $this;
292
    }
293
294
    /**
295
     * Any extra data to attach to the response header
296
     * Available in response->customData(). Useful when using asynchronous requests
297
     * to identify the request
298
     *
299
     * @param mixed $data
300
     *
301
     * @return $this
302
     */
303
    public function customData($data): self
304
    {
305
        return $this->option('user_data', $data);
306
    }
307
308
    /**
309
     * @param string $format
310
     *
311
     * @return $this
312
     */
313
    protected function bodyFormat(string $format): self
314
    {
315
        $this->bodyFormat = $format;
316
317
        return $this;
318
    }
319
320
    /**
321
     * @return $this
322
     */
323
    public function asPlainText()
324
    {
325
        $this->bodyFormat('body');
326
327
        return $this->contentType(self::$CONTENT_TYPE_PLAIN);
328
    }
329
330
    /**
331
     * @return $this
332
     */
333
    public function asJson(): self
334
    {
335
        $this->bodyFormat('json');
336
337
        return $this->contentType(self::$CONTENT_TYPE_JSON);
338
    }
339
340
    /**
341
     * @return $this
342
     */
343
    public function asFormParams(): self
344
    {
345
        $this->bodyFormat('form_params');
346
347
        return $this->contentType(self::$CONTENT_TYPE_FORM_PARAMS);
348
    }
349
350
    /**
351
     * Set the content type multipart form-data
352
     *
353
     * @return $this
354
     */
355
    public function asMultipart(): self
356
    {
357
        $this->bodyFormat('multipart');
358
359
        return $this->contentType(self::$CONTENT_TYPE_MULTIPART);
360
    }
361
362
    /**
363
     * @return array<mixed>
364
     */
365
    public function getHeaders(): array
366
    {
367
        return $this->options['headers'] ?? [];
368
    }
369
370
    /**
371
     * @return null|string
372
     */
373
    public function getContentType()
374
    {
375
        return array_key_exists('Content-Type', $this->getHeaders())
376
            ? $this->getHeaders()['Content-Type']
377
            : null;
378
    }
379
380
    /**
381
     * @return string
382
     */
383
    public function getBodyFormat(): string
384
    {
385
        return $this->bodyFormat;
386
    }
387
388
    /**
389
     * Indicates if the payload must be an array
390
     * depending on body format
391
     *
392
     * @return bool
393
     */
394
    protected function payloadMustBeArray(): bool
395
    {
396
        return in_array(
397
            $this->bodyFormat,
398
            $this->bodyFormats
399
        );
400
    }
401
402
    /**
403
     * @param mixed $payload
404
     *
405
     * @return $this
406
     */
407
    protected function throwExceptionWhenPayloadIsNotArray($payload): self
408
    {
409
        if (! is_array($payload)) {
410
            throw InvalidArgument::payloadMustBeArray();
411
        }
412
413
        return $this;
414
    }
415
416
    /**
417
     * Set any payload text, array
418
     *
419
     * @param mixed $payload
420
     *
421
     * @return $this
422
     */
423
    public function payload($payload): self
424
    {
425
        return $this->option('payload', $payload);
426
    }
427
428
    /**
429
     * @param string $option
430
     *
431
     * @return mixed
432
     */
433
    protected function getOption(string $option)
434
    {
435
        return $this->options[$option] ?? '';
436
    }
437
438
    /**
439
     * @return $this
440
     */
441
    protected function resolvePayload(): self
442
    {
443
        $payload = $this->getOption('payload');
444
445
        if (! is_array($payload) && $this->payloadMustBeArray()) {
446
            throw InvalidArgument::payloadMustBeArray();
447
        }
448
449
        if (empty($payload) || $payload == null) {
450
            $this->removeOptions(['body', 'payload']);
451
452
            return $this;
453
        }
454
455 View Code Duplication
        if ($this->bodyFormat == 'json') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
456
            $this->option('json', $payload);
457
            $this->removeOptions(['body', 'payload']);
458
459
            return $this;
460
        }
461
462
        if ($this->bodyFormat == 'multipart') {
463
            $formData = new FormDataPart($payload);
464
465
            $this->headers($formData->getPreparedHeaders()->toArray());
466
            $this->option('body', $formData->bodyToIterable());
467
            $this->removeOptions(['json', 'payload']);
468
469
            return $this;
470
        }
471
472 View Code Duplication
        if ($this->bodyFormat == 'form_params') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
473
            $this->option('body', $payload);
474
            $this->removeOptions(['json', 'payload']);
475
476
            return $this;
477
        }
478
479
        if (is_string($payload)
480
            || is_resource($payload)
481
            || is_callable($payload)) {
482
483
            $this->option('body', $payload);
484
            $this->removeOptions(['json', 'payload']);
485
486
            return $this;
487
        } else {
488
            throw InvalidArgument::payloadAndBodyFormatNotCompatible($this->bodyFormat);
489
        }
490
    }
491
492
    /**
493
     * remove an option
494
     *
495
     * @param string $option
496
     *
497
     * @return $this
498
     */
499
    protected function removeOption(string $option): self
500
    {
501
        unset($this->options[$option]);
502
503
        return $this;
504
    }
505
506
    /**
507
     * @param array<String> $options
508
     *
509
     * @return $this
510
     */
511
    protected function removeOptions(array $options): self
512
    {
513
        foreach ($options as $option) {
514
            if (! is_string($option)) {
515
                continue;
516
            }
517
518
            $this->removeOption($option);
519
        }
520
521
        return $this;
522
    }
523
524
    /**
525
     * @param mixed $body
526
     *
527
     * @return $this
528
     */
529
    protected function body($body): self
530
    {
531
        return $this->option('body', $body);
532
    }
533
534
}
535