Passed
Pull Request — master (#57)
by Šimon
02:16
created

Http   D

Complexity

Total Complexity 60

Size/Duplication

Total Lines 553
Duplicated Lines 0 %

Test Coverage

Coverage 92.35%

Importance

Changes 0
Metric Value
wmc 60
dl 0
loc 553
ccs 169
cts 183
cp 0.9235
rs 4.2857
c 0
b 0
f 0

28 Methods

Rating   Name   Duplication   Size   Complexity  
A getRequestWrite() 0 4 1
B makeRequest() 0 30 4
A getConnectTimeOut() 0 3 1
A verbose() 0 4 1
A addQueryDegeneration() 0 4 1
A executeAsync() 0 3 1
A getUri() 0 6 2
A getCurler() 0 3 1
A setConnectTimeOut() 0 3 1
C __findXClickHouseProgress() 0 25 8
A cleanQueryDegeneration() 0 4 1
A setHost() 0 7 2
A __construct() 0 9 1
A writeStreamData() 0 18 1
A newRequest() 0 20 3
A prepareWrite() 0 8 2
B getUrl() 0 22 4
A settings() 0 3 1
A getCountPendingQueue() 0 3 1
A prepareQuery() 0 9 2
A setCurler() 0 3 1
A prepareSelect() 0 10 2
B writeAsyncCSV() 0 26 1
C getRequestRead() 0 56 11
A selectAsync() 0 5 1
A setProgressFunction() 0 3 1
A select() 0 5 1
A write() 0 11 3

How to fix   Complexity   

Complex Class

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

1
<?php
2
3
namespace ClickHouseDB\Transport;
4
5
use ClickHouseDB\Query\Degeneration;
6
use ClickHouseDB\Query\Query;
7
use ClickHouseDB\Query\WhereInFile;
8
use ClickHouseDB\Query\WriteToFile;
9
use ClickHouseDB\Settings;
10
use ClickHouseDB\Statement;
11
12
class Http
13
{
14
    /**
15
     * @var string
16
     */
17
    private $_username = null;
18
19
    /**
20
     * @var string
21
     */
22
    private $_password = null;
23
24
    /**
25
     * @var string
26
     */
27
    private $_host = '';
28
29
    /**
30
     * @var int
31
     */
32
    private $_port = 0;
33
34
    /**
35
     * @var bool
36
     */
37
    private $_verbose = false;
38
39
    /**
40
     * @var CurlerRolling
41
     */
42
    private $_curler = false;
43
44
    /**
45
     * @var Settings
46
     */
47
    private $_settings = false;
48
49
    /**
50
     * @var array
51
     */
52
    private $_query_degenerations = [];
53
54
    /**
55
     * Count seconds (int)
56
     *
57
     * @var int
58
     */
59
    private $_connectTimeOut = 5;
60
61
    /**
62
     * @var bool
63
     */
64
    private $xClickHouseProgress=false;
65
66
    /**
67
     * Http constructor.
68
     * @param $host
69
     * @param $port
70
     * @param $username
71
     * @param $password
72
     */
73 43
    public function __construct($host, $port, $username, $password)
74
    {
75 43
        $this->setHost($host, $port);
76
77 43
        $this->_username = $username;
78 43
        $this->_password = $password;
79 43
        $this->_settings = new Settings($this);
80
81 43
        $this->setCurler();
82 43
    }
83
84
85 43
    public function setCurler()
86
    {
87 43
        $this->_curler = new CurlerRolling();
88 43
    }
89
90
    /**
91
     * @return CurlerRolling
92
     */
93
    public function getCurler()
94
    {
95
        return $this->_curler;
96
    }
97
98
    /**
99
     * @param $host
100
     * @param int $port
101
     */
102 43
    public function setHost($host, $port = -1)
103
    {
104 43
        if ($port > 0) {
105 43
            $this->_port = $port;
106
        }
107
108 43
        $this->_host = $host;
109 43
    }
110
111
    /**
112
     * @return string
113
     */
114 35
    public function getUri()
115
    {
116 35
        $proto='http';
117 35
        if ($this->settings()->isHttps()) $proto='https';
118
119 35
        return $proto.'://' . $this->_host . ':' . $this->_port;
120
    }
121
122
    /**
123
     * @return Settings
124
     */
125 43
    public function settings()
126
    {
127 43
        return $this->_settings;
128
    }
129
130
    /**
131
     * @param $flag
132
     * @return mixed
133
     */
134
    public function verbose($flag)
135
    {
136
        $this->_verbose = $flag;
137
        return $flag;
138
    }
139
140
    /**
141
     * @param array $params
142
     * @return string
143
     */
144 35
    private function getUrl($params = [])
145
    {
146 35
        $settings = $this->settings()->getSettings();
147
148 35
        if (is_array($params) && sizeof($params)) {
149 35
            $settings = array_merge($settings, $params);
150
        }
151
152
153 35
        if ($this->settings()->isReadOnlyUser())
154
        {
155
            unset($settings['extremes']);
156
            unset($settings['readonly']);
157
            unset($settings['enable_http_compression']);
158
            unset($settings['max_execution_time']);
159
160
        }
161
162 35
        unset($settings['https']);
163
164
165 35
        return $this->getUri() . '?' . http_build_query($settings);
166
    }
167
168
    /**
169
     * @param $extendinfo
170
     * @return CurlerRequest
171
     */
172 35
    private function newRequest($extendinfo)
173
    {
174 35
        $new = new CurlerRequest();
175 35
        $new->auth($this->_username, $this->_password)
176 35
            ->POST()
177 35
            ->setRequestExtendedInfo($extendinfo);
178
179 35
        if ($this->settings()->isEnableHttpCompression()) {
180 27
            $new->httpCompression(true);
181
        }
182 35
        if ($this->settings()->getSessionId())
183
        {
184 1
            $new->persistent();
185
        }
186
187 35
        $new->timeOut($this->settings()->getTimeOut());
188 35
        $new->connectTimeOut($this->_connectTimeOut)->keepAlive();// one sec
189 35
        $new->verbose($this->_verbose);
190
191 35
        return $new;
192
    }
193
194
    /**
195
     * @param Query $query
196
     * @param array $urlParams
197
     * @param bool $query_as_string
198
     * @return CurlerRequest
199
     * @throws \ClickHouseDB\Exception\TransportException
200
     */
201 35
    private function makeRequest(Query $query, $urlParams = [], $query_as_string = false)
202
    {
203 35
        $sql = $query->toSql();
204
205 35
        if ($query_as_string) {
206 1
            $urlParams['query'] = $sql;
207
        }
208
209 35
        $url = $this->getUrl($urlParams);
210
211
        $extendinfo = [
212 35
            'sql' => $sql,
213 35
            'query' => $query,
214 35
            'format'=> $query->getFormat()
215
        ];
216
217 35
        $new = $this->newRequest($extendinfo);
218 35
        $new->url($url);
219
220
221
222
223 35
        if (!$query_as_string) {
224 35
            $new->parameters_json($sql);
225
        }
226 35
        if ($this->settings()->isEnableHttpCompression()) {
227 27
            $new->httpCompression(true);
228
        }
229
230 35
        return $new;
231
    }
232
233
    /**
234
     * @param $sql
235
     * @return CurlerRequest
236
     */
237 2
    public function writeStreamData($sql)
238
    {
239 2
        $query = new Query($sql);
240
241 2
        $url = $this->getUrl([
242 2
            'readonly' => 0,
243 2
            'query' => $query->toSql()
244
        ]);
245
246
        $extendinfo = [
247 2
            'sql' => $sql,
248 2
            'query' => $query,
249 2
            'format'=> $query->getFormat()
250
        ];
251
252 2
        $request = $this->newRequest($extendinfo);
253 2
        $request->url($url);
254 2
        return $request;
255
    }
256
257
258
    /**
259
     * @param $sql
260
     * @param $file_name
261
     * @return Statement
262
     * @throws \ClickHouseDB\Exception\TransportException
263
     */
264 7
    public function writeAsyncCSV($sql, $file_name)
265
    {
266 7
        $query = new Query($sql);
267
268 7
        $url = $this->getUrl([
269 7
            'readonly' => 0,
270 7
            'query' => $query->toSql()
271
        ]);
272
273
        $extendinfo = [
274 7
            'sql' => $sql,
275 7
            'query' => $query,
276 7
            'format'=> $query->getFormat()
277
        ];
278
279 7
        $request = $this->newRequest($extendinfo);
280 7
        $request->url($url);
281
282
        $request->setCallbackFunction(function (CurlerRequest $request) {
283 7
            fclose($request->getInfileHandle());
0 ignored issues
show
Bug introduced by
$request->getInfileHandle() of type boolean is incompatible with the type resource expected by parameter $handle of fclose(). ( Ignorable by Annotation )

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

283
            fclose(/** @scrutinizer ignore-type */ $request->getInfileHandle());
Loading history...
284 7
        });
285
286 7
        $request->setInfile($file_name);
287 7
        $this->_curler->addQueLoop($request);
288
289 7
        return new Statement($request);
290
    }
291
292
    /**
293
     * get Count Pending Query in Queue
294
     *
295
     * @return int
296
     */
297 9
    public function getCountPendingQueue()
298
    {
299 9
        return $this->_curler->countPending();
300
    }
301
302
    /**
303
     * set Connect TimeOut in seconds [CURLOPT_CONNECTTIMEOUT] ( int )
304
     *
305
     * @param int $connectTimeOut
306
     */
307 2
    public function setConnectTimeOut($connectTimeOut)
308
    {
309 2
        $this->_connectTimeOut = $connectTimeOut;
310 2
    }
311
312
    /**
313
     * get ConnectTimeOut in seconds
314
     *
315
     * @return int
316
     */
317 1
    public function getConnectTimeOut()
318
    {
319 1
        return $this->_connectTimeOut;
320
    }
321
322
323 1
    public function __findXClickHouseProgress($handle)
324
    {
325 1
        $code=curl_getinfo($handle,CURLINFO_HTTP_CODE);
326
327
        // Search X-ClickHouse-Progress
328 1
        if ($code==200) {
329 1
            $response = curl_multi_getcontent($handle);
330 1
            $header_size = curl_getinfo($handle, CURLINFO_HEADER_SIZE);
331 1
            if (!$header_size) return false;
332
333 1
            $header = substr($response, 0, $header_size);
334 1
            if (!$header_size) return false;
335 1
            $pos=strrpos($header,'X-ClickHouse-Progress');
336
337 1
            if (!$pos) return false;
338
339 1
            $last=substr($header,$pos);
340 1
            $data=@json_decode(str_ireplace('X-ClickHouse-Progress:','',$last),true);
341
342 1
            if ($data && is_callable($this->xClickHouseProgress)) {
343
344 1
                if (is_array($this->xClickHouseProgress)){
0 ignored issues
show
introduced by
The condition is_array($this->xClickHouseProgress) is always false.
Loading history...
345
                    call_user_func_array($this->xClickHouseProgress,[$data]);
346
                } else {
347 1
                    call_user_func($this->xClickHouseProgress,$data);
0 ignored issues
show
Bug introduced by
$this->xClickHouseProgress of type boolean is incompatible with the type callable expected by parameter $function of call_user_func(). ( Ignorable by Annotation )

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

347
                    call_user_func(/** @scrutinizer ignore-type */ $this->xClickHouseProgress,$data);
Loading history...
348
                }
349
350
351
            }
352
353
        }
354
355 1
    }
356
357
    /**
358
     * @param Query $query
359
     * @param null $whereInFile
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $writeToFile is correct as it would always require null to be passed?
Loading history...
Documentation Bug introduced by
Are you sure the doc-type for parameter $whereInFile is correct as it would always require null to be passed?
Loading history...
360
     * @param null $writeToFile
361
     * @return CurlerRequest
362
     * @throws \Exception
363
     */
364 35
    public function getRequestRead(Query $query, $whereInFile = null, $writeToFile = null)
365
    {
366 35
        $urlParams = ['readonly' => 1];
367 35
        $query_as_string = false;
368
        // ---------------------------------------------------------------------------------
369 35
        if ($whereInFile instanceof WhereInFile && $whereInFile->size()) {
370
            // $request = $this->prepareSelectWhereIn($request, $whereInFile);
371 1
            $structure = $whereInFile->fetchUrlParams();
372
            // $structure = [];
373 1
            $urlParams = array_merge($urlParams, $structure);
374 1
            $query_as_string = true;
375
        }
376
        // ---------------------------------------------------------------------------------
377
        // if result to file
378 35
        if ($writeToFile instanceof WriteToFile && $writeToFile->fetchFormat()) {
379 1
            $query->setFormat($writeToFile->fetchFormat());
380 1
            unset($urlParams['extremes']);
381
        }
382
        // ---------------------------------------------------------------------------------
383
        // makeRequest read
384 35
        $request = $this->makeRequest($query, $urlParams, $query_as_string);
385
        // ---------------------------------------------------------------------------------
386
        // attach files
387 35
        if ($whereInFile instanceof WhereInFile && $whereInFile->size()) {
388 1
            $request->attachFiles($whereInFile->fetchFiles());
389
        }
390
        // ---------------------------------------------------------------------------------
391
        // result to file
392 35
        if ($writeToFile instanceof WriteToFile && $writeToFile->fetchFormat()) {
393
394 1
            $fout = fopen($writeToFile->fetchFile(), 'w');
395 1
            $isGz = $writeToFile->getGzip();
396
397 1
            if ($isGz) {
398
                // write gzip header
399
                // "\x1f\x8b\x08\x00\x00\x00\x00\x00"
400
                // fwrite($fout, "\x1F\x8B\x08\x08".pack("V", time())."\0\xFF", 10);
401
                // write the original file name
402
                // $oname = str_replace("\0", "", basename($writeToFile->fetchFile()));
403
                // fwrite($fout, $oname."\0", 1+strlen($oname));
404
405
                fwrite($fout, "\x1f\x8b\x08\x00\x00\x00\x00\x00");
0 ignored issues
show
Bug introduced by
It seems like $fout can also be of type false; however, parameter $handle of fwrite() 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

405
                fwrite(/** @scrutinizer ignore-type */ $fout, "\x1f\x8b\x08\x00\x00\x00\x00\x00");
Loading history...
406
407
            }
408
409
410
            $request->setResultFileHandle($fout, $isGz)->setCallbackFunction(function (CurlerRequest $request) {
411
                fclose($request->getResultFileHandle());
412 1
            });
413
        }
414 35
        if ($this->xClickHouseProgress)
415
        {
416 1
            $request->setFunctionProgress([$this,'__findXClickHouseProgress']);
417
        }
418
        // ---------------------------------------------------------------------------------
419 35
        return $request;
420
421
    }
422
423 1
    public function cleanQueryDegeneration()
424
    {
425 1
        $this->_query_degenerations = [];
426 1
        return true;
427
    }
428
429 43
    public function addQueryDegeneration(Degeneration $degeneration)
430
    {
431 43
        $this->_query_degenerations[] = $degeneration;
432 43
        return true;
433
    }
434
435
    /**
436
     * @param Query $query
437
     * @return CurlerRequest
438
     */
439 17
    public function getRequestWrite(Query $query)
440
    {
441 17
        $urlParams = ['readonly' => 0];
442 17
        return $this->makeRequest($query, $urlParams);
443
    }
444
445
    /**
446
     * @param $sql
447
     * @param $bindings
448
     * @return Query
449
     */
450 35
    private function prepareQuery($sql, $bindings)
451
    {
452
453
        // add Degeneration query
454 35
        foreach ($this->_query_degenerations as $degeneration) {
455 35
            $degeneration->bindParams($bindings);
456
        }
457
458 35
        return new Query($sql, $this->_query_degenerations);
459
    }
460
461
462
    /**
463
     * @param $sql
464
     * @param $bindings
465
     * @param $whereInFile
466
     * @param null $writeToFile
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $writeToFile is correct as it would always require null to be passed?
Loading history...
467
     * @return CurlerRequest
468
     * @throws \Exception
469
     */
470 35
    private function prepareSelect($sql, $bindings, $whereInFile, $writeToFile = null)
471
    {
472 35
        if ($sql instanceof Query) {
473
            return $this->getRequestWrite($sql);
474
        }
475
476
477 35
        $query = $this->prepareQuery($sql, $bindings);
478 35
        $query->setFormat('JSON');
479 35
        return $this->getRequestRead($query, $whereInFile, $writeToFile);
480
481
    }
482
483
    /**
484
     * @param $sql
485
     * @param $bindings
486
     * @return CurlerRequest
487
     */
488 18
    private function prepareWrite($sql, $bindings = [])
489
    {
490 18
        if ($sql instanceof Query) {
491
            return $this->getRequestWrite($sql);
492
        }
493
494 18
        $query = $this->prepareQuery($sql, $bindings);
495 17
        return $this->getRequestWrite($query);
496
    }
497
498
    /**
499
     *
500
     * @return bool
501
     */
502 8
    public function executeAsync()
503
    {
504 8
        return $this->_curler->execLoopWait();
505
    }
506
507
    /**
508
     * @param $sql
509
     * @param array $bindings
510
     * @param null|WhereInFile $whereInFile
511
     * @param null|WriteToFile $writeToFile
512
     * @return Statement
513
     * @throws \ClickHouseDB\Exception\TransportException
514
     * @throws \Exception
515
     */
516 34
    public function select($sql, array $bindings = [], $whereInFile = null, $writeToFile = null)
517
    {
518 34
        $request = $this->prepareSelect($sql, $bindings, $whereInFile, $writeToFile);
0 ignored issues
show
Bug introduced by
It seems like $writeToFile can also be of type ClickHouseDB\Query\WriteToFile; however, parameter $writeToFile of ClickHouseDB\Transport\Http::prepareSelect() does only seem to accept null, 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

518
        $request = $this->prepareSelect($sql, $bindings, $whereInFile, /** @scrutinizer ignore-type */ $writeToFile);
Loading history...
519 34
        $this->_curler->execOne($request);
520 34
        return new Statement($request);
521
    }
522
523
    /**
524
     * @param $sql
525
     * @param array $bindings
526
     * @param null $whereInFile
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $whereInFile is correct as it would always require null to be passed?
Loading history...
Documentation Bug introduced by
Are you sure the doc-type for parameter $writeToFile is correct as it would always require null to be passed?
Loading history...
527
     * @param null $writeToFile
528
     * @return Statement
529
     * @throws \ClickHouseDB\Exception\TransportException
530
     * @throws \Exception
531
     */
532 4
    public function selectAsync($sql, array $bindings = [], $whereInFile = null, $writeToFile = null)
533
    {
534 4
        $request = $this->prepareSelect($sql, $bindings, $whereInFile, $writeToFile);
535 4
        $this->_curler->addQueLoop($request);
536 4
        return new Statement($request);
537
    }
538
539
    /**
540
     * @param $callback
541
     */
542 1
    public function setProgressFunction(callable $callback)
543
    {
544 1
        $this->xClickHouseProgress=$callback;
0 ignored issues
show
Documentation Bug introduced by
It seems like $callback of type callable is incompatible with the declared type boolean of property $xClickHouseProgress.

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...
545 1
    }
546
547
    /**
548
     * @param $sql
549
     * @param array $bindings
550
     * @param bool $exception
551
     * @return Statement
552
     * @throws \ClickHouseDB\Exception\TransportException
553
     */
554 18
    public function write($sql, array $bindings = [], $exception = true)
555
    {
556 18
        $request = $this->prepareWrite($sql, $bindings);
557 17
        $this->_curler->execOne($request);
558 17
        $response = new Statement($request);
559 17
        if ($exception) {
560 17
            if ($response->isError()) {
561 3
                $response->error();
562
            }
563
        }
564 15
        return $response;
565
    }
566
}
567