Passed
Branch master (8a55cf)
by Igor
12:47 queued 09:06
created

CurlerRequest   F

Complexity

Total Complexity 81

Size/Duplication

Total Lines 706
Duplicated Lines 0 %

Test Coverage

Coverage 74.57%

Importance

Changes 0
Metric Value
wmc 81
dl 0
loc 706
ccs 173
cts 232
cp 0.7457
rs 1.894
c 0
b 0
f 0

48 Methods

Rating   Name   Duplication   Size   Complexity  
A keepAlive() 0 7 1
A httpCompression() 0 9 2
A close() 0 7 2
A getUniqHash() 0 3 1
A isResultFile() 0 3 2
A parameters_json() 0 23 5
A setReadFunction() 0 3 1
A isPersistent() 0 3 1
A dump() 0 15 2
A setInfile() 0 19 2
F prepareRequest() 0 60 13
A GET() 0 3 1
A url() 0 4 1
A id() 0 7 2
A getId() 0 3 1
A setResponse() 0 3 1
A getResultFileHandle() 0 3 1
A execute() 0 4 1
A OPTIONS() 0 3 1
A timeOut() 0 3 1
A __destruct() 0 3 1
A auth() 0 4 1
A timeOutMs() 0 4 1
A persistent() 0 4 1
A __construct() 0 21 1
A getUrl() 0 3 1
A setFunctionProgress() 0 8 2
A isResponseExists() 0 3 2
A getInfileHandle() 0 3 1
A header() 0 4 1
A onCallback() 0 10 4
A getHeaders() 0 7 2
A parameters() 0 4 1
A connectTimeOut() 0 4 1
A attachFiles() 0 11 2
A setRequestExtendedInfo() 0 4 1
A option() 0 4 1
A handle() 0 4 1
A POST() 0 3 1
A getRequestExtendedInfo() 0 7 3
A setCallback() 0 4 1
A response() 0 7 2
A verbose() 0 4 1
A getDnsCache() 0 3 1
A setDnsCache() 0 4 1
A setCallbackFunction() 0 3 1
A PUT() 0 3 1
A setResultFileHandle() 0 8 2

How to fix   Complexity   

Complex Class

Complex classes like CurlerRequest 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 CurlerRequest, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace ClickHouseDB\Transport;
4
5
class CurlerRequest
6
{
7
    /**
8
     * @var array
9
     */
10
    public $extendinfo = array();
11
12
    /**
13
     * @var string|array
14
     */
15
    private $parameters = '';
16
17
    /**
18
     * @var array
19
     */
20
    private $options;
21
22
    /**
23
     * @var array
24
     */
25
    private $headers; // Parsed reponse header object.
26
27
    /**
28
     * @var string
29
     */
30
    private $url;
31
32
    /**
33
     * @var string
34
     */
35
    private $method;
36
37
    /**
38
     * @var bool
39
     */
40
    private $id;
41
42
    /**
43
     * @var resource|null
44
     */
45
    private $handle;
46
47
    /**
48
     * @var CurlerResponse
49
     */
50
    private $resp = null;
51
52
    /**
53
     * @var bool
54
     */
55
    private $_persistent = false;
56
57
    /**
58
     * @var bool
59
     */
60
    private $_attachFiles = false;
61
62
    /**
63
     * @var string
64
     */
65
    private $callback_class = '';
66
67
    /**
68
     * @var string
69
     */
70
    private $callback_functionName = '';
71
72
    /**
73
     * @var bool
74
     */
75
    private $_httpCompression = false;
76
77
    /**
78
     * @var callable
79
     */
80
    private $callback_function = null;
81
82
    /**
83
     * @var bool
84
     */
85
    private $infile_handle = false;
86
87
    /**
88
     * @var int
89
     */
90
    private $_dns_cache = 120;
91
92
    /**
93
     * @var resource
94
     */
95
    private $resultFileHandle = null;
96
97
    /**
98
     * @param bool $id
99
     */
100 36
    public function __construct($id = false)
101
    {
102 36
        $this->id = $id;
103
104 36
        $this->header('Cache-Control', 'no-cache, no-store, must-revalidate');
105 36
        $this->header('Expires', '0');
106 36
        $this->header('Pragma', 'no-cache');
107
108 36
        $this->options = array(
109 36
            CURLOPT_SSL_VERIFYHOST => 0,
110 36
            CURLOPT_SSL_VERIFYPEER => false,
111 36
            CURLOPT_TIMEOUT => 10,
112 36
            CURLOPT_CONNECTTIMEOUT => 5, // Количество секунд ожидания при попытке соединения
113 36
            CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
114 36
            CURLOPT_MAXREDIRS => 10,
115 36
            CURLOPT_HEADER => TRUE,
116 36
            CURLOPT_FOLLOWLOCATION => TRUE,
117 36
            CURLOPT_AUTOREFERER => 1, // при редиректе подставлять в «Referer:» значение из «Location:»
118 36
            CURLOPT_BINARYTRANSFER => 1, // передавать в binary-safe
119 36
            CURLOPT_RETURNTRANSFER => TRUE,
120 36
            CURLOPT_USERAGENT => 'smi2/PHPClickHouse/client',
121
        );
122 36
    }
123
124
    /**
125
     *
126
     */
127 35
    public function __destruct()
128
    {
129 35
        $this->close();
130 35
    }
131
132
133 35
    public function close()
134
    {
135 35
        if ($this->handle)
136
        {
137 35
            curl_close($this->handle);
138
        }
139 35
        $this->handle = null;
140 35
    }
141
142
    /**
143
     * @param array $attachFiles
144
     */
145 1
    public function attachFiles($attachFiles)
146
    {
147 1
        $this->header("Content-Type", "multipart/form-data");
148
149 1
        $out = [];
150 1
        foreach ($attachFiles as $post_name => $file_path) {
151 1
            $out[$post_name] = new \CURLFile($file_path);
152
        }
153
154 1
        $this->_attachFiles = true;
155 1
        $this->parameters($out);
156 1
    }
157
158
159
    /**
160
     * @param bool $set
161
     * @return $this
162
     */
163
    public function id($set = false)
164
    {
165
        if ($set) {
166
            $this->id = $set;
167
        }
168
169
        return $this;
170
    }
171
172
    /**
173
     * @param array $params
174
     * @return $this
175
     */
176 36
    public function setRequestExtendedInfo($params)
177
    {
178 36
        $this->extendinfo = $params;
179 36
        return $this;
180
    }
181
182
    /**
183
     * @param string|integer|null $key
184
     * @return mixed
185
     */
186 36
    public function getRequestExtendedInfo($key = null)
187
    {
188 36
        if ($key) {
189 36
            return isset($this->extendinfo[$key]) ? $this->extendinfo[$key] : false;
190
        }
191
192
        return $this->extendinfo;
193
    }
194
195
    /**
196
     * @return bool|resource
197
     */
198 7
    public function getInfileHandle()
199
    {
200 7
        return $this->infile_handle;
201
    }
202
203
    /**
204
     * @param string $file_name
205
     * @return bool
206
     */
207 7
    public function setInfile($file_name)
208
    {
209 7
        $this->header('Expect', '');
210 7
        $this->infile_handle = fopen($file_name, 'r');
0 ignored issues
show
Documentation Bug introduced by
It seems like fopen($file_name, 'r') can also be of type resource. However, the property $infile_handle is declared as type boolean. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
211
212 7
        if ($this->_httpCompression) {
213 7
            $this->header('Content-Encoding', 'gzip');
214 7
            $this->header('Content-Type', 'application/x-www-form-urlencoded');
215
216 7
            stream_filter_append($this->infile_handle, 'zlib.deflate', STREAM_FILTER_READ, ["window" => 30]);
0 ignored issues
show
Bug introduced by
It seems like $this->infile_handle can also be of type false; however, parameter $stream of stream_filter_append() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

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

216
            stream_filter_append(/** @scrutinizer ignore-type */ $this->infile_handle, 'zlib.deflate', STREAM_FILTER_READ, ["window" => 30]);
Loading history...
217
218 7
            $this->options[CURLOPT_SAFE_UPLOAD] = 1;
219
        } else {
220
            $this->options[CURLOPT_INFILESIZE] = filesize($file_name);
221
        }
222
223 7
        $this->options[CURLOPT_INFILE] = $this->infile_handle;
224
225 7
        return $this->infile_handle;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->infile_handle also could return the type resource which is incompatible with the documented return type boolean.
Loading history...
226
    }
227
228
    /**
229
     * @param callable $callback
230
     */
231 7
    public function setCallbackFunction($callback)
232
    {
233 7
        $this->callback_function = $callback;
234 7
    }
235
236
    /**
237
     * @param callable $callback
238
     */
239 2
    public function setReadFunction($callback)
240
    {
241 2
        $this->options[CURLOPT_READFUNCTION] = $callback;
242 2
    }
243
244
    /**
245
     * @param string $classCallBack
246
     * @param string $functionName
247
     */
248
    public function setCallback($classCallBack, $functionName)
249
    {
250
        $this->callback_class = $classCallBack;
251
        $this->callback_functionName = $functionName;
252
    }
253
254
    /**
255
     *
256
     */
257 8
    public function onCallback()
258
    {
259 8
        if ($this->callback_function) {
260 7
            $x = $this->callback_function;
261 7
            $x($this);
262
        }
263
264 8
        if ($this->callback_class && $this->callback_functionName) {
265
            $c = $this->callback_functionName;
266
            $this->callback_class->$c($this);
267
        }
268 8
    }
269
270
    /**
271
     * @param bool $result
272
     * @return string
273
     */
274
    public function dump($result = false)
275
    {
276
        $message = "\n------------  Request ------------\n";
277
        $message .= 'URL:' . $this->url . "\n\n";
278
        $message .= 'METHOD:' . $this->method . "\n\n";
279
        $message .= 'PARAMS:' . print_r($this->parameters, true) . "\n";
280
        $message .= 'PARAMS:' . print_r($this->headers, true) . "\n";
281
        $message .= "-----------------------------------\n";
282
283
        if ($result) {
284
            return $message;
285
        }
286
287
        echo $message;
288
        return '';
289
    }
290
291
    /**
292
     * @return bool
293
     */
294 11
    public function getId()
295
    {
296 11
        return $this->id;
297
    }
298
299
    /**
300
     * @param integer $key
301
     * @param mixed $value
302
     * @return $this
303
     */
304 1
    private function option($key, $value)
305
    {
306 1
        $this->options[$key] = $value;
307 1
        return $this;
308
    }
309
310
    /**
311
     * @return $this
312
     */
313 1
    public function persistent()
314
    {
315 1
        $this->_persistent = true;
316 1
        return $this;
317
    }
318
319
    /**
320
     * @return bool
321
     */
322 8
    public function isPersistent()
323
    {
324 8
        return $this->_persistent;
325
    }
326
327
    /**
328
     * @param int $sec
329
     * @return $this
330
     */
331 36
    public function keepAlive($sec = 60)
332
    {
333 36
        $this->options[CURLOPT_FORBID_REUSE] = TRUE;
334 36
        $this->headers['Connection'] = 'Keep-Alive';
335 36
        $this->headers['Keep-Alive'] = $sec;
336
337 36
        return $this;
338
    }
339
340
    /**
341
     * @param bool $flag
342
     * @return $this
343
     */
344 36
    public function verbose($flag = true)
345
    {
346 36
        $this->options[CURLOPT_VERBOSE] = $flag;
347 36
        return $this;
348
    }
349
350
    /**
351
     * @param string $key
352
     * @param string $value
353
     * @return $this
354
     */
355 36
    public function header($key, $value)
356
    {
357 36
        $this->headers[$key] = $value;
358 36
        return $this;
359
    }
360
361
    /**
362
     * @return array
363
     */
364
    public function getHeaders()
365
    {
366
        $head = [];
367
        foreach ($this->headers as $key=>$value) {
368
                    $head[] = sprintf("%s: %s", $key, $value);
369
        }
370
        return $head;
371
    }
372
373
    /**
374
     * @param string $url
375
     * @return $this
376
     */
377 36
    public function url($url)
378
    {
379 36
        $this->url = $url;
380 36
        return $this;
381
    }
382
383
    /**
384
     * @return mixed
385
     */
386
    public function getUrl()
387
    {
388
        return $this->url;
389
    }
390
391
392
    /**
393
     * @param string $id
394
     * @return string
395
     */
396 11
    public function getUniqHash($id)
397
    {
398 11
        return $id . '.' . microtime() . mt_rand(0, 1000000);
399
    }
400
401
    /**
402
     * @param bool $flag
403
     */
404 27
    public function httpCompression($flag)
405
    {
406 27
        if ($flag) {
407 27
            $this->_httpCompression = $flag;
408 27
            $this->options[CURLOPT_ENCODING] = 'gzip';
409
        } else
410
        {
411
            $this->_httpCompression = false;
412
            unset($this->options[CURLOPT_ENCODING]);
413
        }
414 27
    }
415
416
    /**
417
     * @param string $username
418
     * @param string $password
419
     * @return $this
420
     */
421 36
    public function auth($username, $password)
422
    {
423 36
        $this->options[CURLOPT_USERPWD] = sprintf("%s:%s", $username, $password);
424 36
        return $this;
425
    }
426
427
    /**
428
     * @param array|string $data
429
     * @return $this
430
     */
431 1
    public function parameters($data)
432
    {
433 1
        $this->parameters = $data;
434 1
        return $this;
435
    }
436
437
    /**
438
     * The number of seconds to wait when trying to connect. Use 0 for infinite waiting.
439
     *
440
     * @param int $seconds
441
     * @return $this
442
     */
443 36
    public function connectTimeOut($seconds = 1)
444
    {
445 36
        $this->options[CURLOPT_CONNECTTIMEOUT] = $seconds;
446 36
        return $this;
447
    }
448
449
    /**
450
     * The maximum number of seconds (float) allowed to execute cURL functions.
451
     *
452
     * @param float $seconds
453
     * @return $this
454
     */
455 36
    public function timeOut($seconds = 10)
456
    {
457 36
        return $this->timeOutMs(intval($seconds * 1000));
458
    }
459
460
    /**
461
     * The maximum allowed number of milliseconds to perform cURL functions.
462
     *
463
     * @param int $ms millisecond
464
     * @return $this
465
     */
466 36
    protected function timeOutMs($ms = 10000)
467
    {
468 36
        $this->options[CURLOPT_TIMEOUT_MS] = $ms;
469 36
        return $this;
470
    }
471
472
473
    /**
474
     * @param array|mixed $data
475
     * @return $this
476
     * @throws \ClickHouseDB\Exception\TransportException
477
     */
478 36
    public function parameters_json($data)
479
    {
480
481 36
        $this->header("Content-Type", "application/json, text/javascript; charset=utf-8");
482 36
        $this->header("Accept", "application/json, text/javascript, */*; q=0.01");
483
484 36
        if ($data === null) {
485
            $this->parameters = '{}';
486
            return $this;
487
        }
488
489 36
        if (is_string($data)) {
490 36
            $this->parameters = $data;
491 36
            return $this;
492
        }
493
494
        $this->parameters = json_encode($data);
495
496
        if (!$this->parameters && $data) {
497
            throw new \ClickHouseDB\Exception\TransportException('Cant json_encode: ' . $data);
0 ignored issues
show
Bug introduced by
Are you sure $data of type array|mixed can be used in concatenation? ( Ignorable by Annotation )

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

497
            throw new \ClickHouseDB\Exception\TransportException('Cant json_encode: ' . /** @scrutinizer ignore-type */ $data);
Loading history...
498
        }
499
500
        return $this;
501
    }
502
503
    /**
504
     * @return resource
505
     */
506
    public function getResultFileHandle()
507
    {
508
        return $this->resultFileHandle;
509
    }
510
511
    /**
512
     * @return bool
513
     */
514
    public function isResultFile()
515
    {
516
        return ($this->resultFileHandle ? true : false);
517
    }
518
519
    /**
520
     * @param resource $h resource
521
     * @param bool $zlib
522
     * @return $this
523
     */
524 1
    public function setResultFileHandle($h, $zlib = false)
525
    {
526 1
        $this->resultFileHandle = $h;
527 1
        if ($zlib) {
528
            $params = array('level' => 6, 'window' => 15, 'memory' => 9);
529
            stream_filter_append($this->resultFileHandle, 'zlib.deflate', STREAM_FILTER_WRITE, $params);
530
        }
531 1
        return $this;
532
    }
533
534
    /**
535
     * @return CurlerRequest
536
     */
537
    public function PUT()
538
    {
539
        return $this->execute('PUT');
540
    }
541
542
    /**
543
     * @return CurlerRequest
544
     */
545 36
    public function POST()
546
    {
547 36
        return $this->execute('POST');
548
    }
549
550
    /**
551
     * @return CurlerRequest
552
     */
553
    public function OPTIONS()
554
    {
555
        return $this->execute('OPTIONS');
556
    }
557
558
    /**
559
     * @return CurlerRequest
560
     */
561
    public function GET()
562
    {
563
        return $this->execute('GET');
564
    }
565
566
    /**
567
     * The number of seconds that DNS records are stored in memory. By default this parameter is 120 (2 minutes).
568
     *
569
     * @param integer $set
570
     * @return $this
571
     */
572
    public function setDnsCache($set)
573
    {
574
        $this->_dns_cache = $set;
575
        return $this;
576
    }
577
578
    /**
579
     * The number of seconds that DNS records are stored in memory. By default this parameter is 120 (2 minutes).
580
     *
581
     * @return int
582
     */
583 35
    public function getDnsCache()
584
    {
585 35
        return $this->_dns_cache;
586
    }
587
588
    /**
589
     * @param string $method
590
     * @return $this
591
     */
592 36
    private function execute($method)
593
    {
594 36
        $this->method = $method;
595 36
        return $this;
596
    }
597
598
    /**
599
     * @return CurlerResponse
600
     * @throws \ClickHouseDB\Exception\TransportException
601
     */
602 35
    public function response()
603
    {
604 35
        if (!$this->resp) {
605
            throw new \ClickHouseDB\Exception\TransportException('Can`t fetch response - is empty');
606
        }
607
608 35
        return $this->resp;
609
    }
610
611
    /**
612
     * @return bool
613
     */
614 35
    public function isResponseExists()
615
    {
616 35
        return ($this->resp ? true : false);
617
    }
618
619 35
    public function setResponse(CurlerResponse $response)
620
    {
621 35
        $this->resp = $response;
622 35
    }
623
624
    /**
625
     * @return mixed
626
     */
627 35
    public function handle()
628
    {
629 35
        $this->prepareRequest();
630 35
        return $this->handle;
631
    }
632
633
    /**
634
     * @param callable $callback
635
     * @throws \Exception
636
     */
637 1
    public function setFunctionProgress(callable $callback)
638
    {
639 1
        if (!is_callable($callback)) {
640
            throw new \Exception('setFunctionProgress not is_callable');
641
        }
642
643 1
        $this->option(CURLOPT_NOPROGRESS, false);
644 1
        $this->option(CURLOPT_PROGRESSFUNCTION, $callback); // version 5.5.0
645 1
    }
646
647
648
    /**
649
     * @return bool
650
     */
651 35
    private function prepareRequest()
652
    {
653 35
        if (!$this->handle) {
654 35
            $this->handle = curl_init();
655
        }
656
657 35
        $curl_opt = $this->options;
658 35
        $method = $this->method;
659
660 35
        if ($this->_attachFiles) {
661 1
            $curl_opt[CURLOPT_SAFE_UPLOAD] = true;
662
        }
663
664
665 35
        if (strtoupper($method) == 'GET') {
666
            $curl_opt[CURLOPT_HTTPGET] = TRUE;
667
            $curl_opt[CURLOPT_CUSTOMREQUEST] = strtoupper($method);
668
            $curl_opt[CURLOPT_POSTFIELDS] = false;
669
        } else {
670 35
            if (strtoupper($method) === 'POST') {
671 35
                $curl_opt[CURLOPT_POST] = TRUE;
672
            }
673
674 35
            $curl_opt[CURLOPT_CUSTOMREQUEST] = strtoupper($method);
675
676 35
            if ($this->parameters) {
677 35
                $curl_opt[CURLOPT_POSTFIELDS] = $this->parameters;
678
679 35
                if (!is_array($this->parameters)) {
680 35
                    $this->header('Content-Length', strlen($this->parameters));
681
                }
682
            }
683
        }
684
        // CURLOPT_DNS_CACHE_TIMEOUT - Количество секунд, в течение которых в памяти хранятся DNS-записи.
685 35
        $curl_opt[CURLOPT_DNS_CACHE_TIMEOUT] = $this->getDnsCache();
686 35
        $curl_opt[CURLOPT_URL] = $this->url;
687
688 35
        if ($this->headers && sizeof($this->headers)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->headers of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
689 35
            $curl_opt[CURLOPT_HTTPHEADER] = array();
690
691 35
            foreach ($this->headers as $key => $value) {
692 35
                $curl_opt[CURLOPT_HTTPHEADER][] = sprintf("%s: %s", $key, $value);
693
            }
694
        }
695
696 35
        if (!empty($curl_opt[CURLOPT_INFILE])) {
697
698 7
            $curl_opt[CURLOPT_PUT] = true;
699
        }
700
701 35
        if ($this->resultFileHandle) {
702 1
            $curl_opt[CURLOPT_FILE] = $this->resultFileHandle;
703 1
            $curl_opt[CURLOPT_HEADER] = false;
704
        }
705
706 35
        if ($this->options[CURLOPT_VERBOSE]) {
707
            echo "\n-----------BODY REQUEST----------\n" . $curl_opt[CURLOPT_POSTFIELDS] . "\n------END--------\n";
708
        }
709 35
        curl_setopt_array($this->handle, $curl_opt);
710 35
        return true;
711
    }
712
}
713