Passed
Push — master ( 4e99ce...4c2c3a )
by Igor
05:31
created

Statement   D

Complexity

Total Complexity 58

Size/Duplication

Total Lines 511
Duplicated Lines 0 %

Test Coverage

Coverage 72.05%

Importance

Changes 0
Metric Value
dl 0
loc 511
ccs 116
cts 161
cp 0.7205
rs 4.8387
c 0
b 0
f 0
wmc 58

28 Methods

Rating   Name   Duplication   Size   Complexity  
B array_to_tree() 0 34 6
A extremes() 0 4 1
A dump() 0 4 1
A response() 0 3 1
A info() 0 11 1
A extremesMax() 0 9 2
A rowsAsTree() 0 11 2
A statistics() 0 10 4
A extremesMin() 0 9 2
A parseErrorClickHouse() 0 14 2
A isError() 0 3 2
A totals() 0 4 1
C init() 0 38 8
A countAll() 0 4 1
A info_upload() 0 8 1
A totalTimeRequest() 0 4 1
A rawData() 0 9 2
A count() 0 4 1
A sql() 0 3 1
A getRequest() 0 3 1
A check() 0 11 3
A dumpRaw() 0 3 1
A fetchOne() 0 18 4
A getFormat() 0 3 1
A responseInfo() 0 3 1
A __construct() 0 6 1
B error() 0 27 5
A rows() 0 4 1

How to fix   Complexity   

Complex Class

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

1
<?php
2
3
namespace ClickHouseDB;
4
5
use ClickHouseDB\Exception\DatabaseException;
6
use ClickHouseDB\Exception\QueryException;
7
use ClickHouseDB\Query\Query;
8
use ClickHouseDB\Transport\CurlerRequest;
9
use ClickHouseDB\Transport\CurlerResponse;
10
11
class Statement
12
{
13
    /**
14
     * @var string
15
     */
16
    private $_rawData;
17
18
    /**
19
     * @var int
20
     */
21
    private $_http_code = -1;
22
23
    /**
24
     * @var CurlerRequest
25
     */
26
    private $_request = null;
27
28
    /**
29
     * @var bool
30
     */
31
    private $_init = false;
32
33
    /**
34
     * @var Query
35
     */
36
    private $query;
37
38
    /**
39
     * @var mixed
40
     */
41
    private $format;
42
43
    /**
44
     * @var string
45
     */
46
    private $sql = '';
47
48
    /**
49
     * @var array
50
     */
51
    private $meta;
52
53
    /**
54
     * @var array
55
     */
56
    private $data;
57
58
    /**
59
     * @var array
60
     */
61
    private $totals;
62
63
    /**
64
     * @var array
65
     */
66
    private $extremes;
67
68
    /**
69
     * @var int
70
     */
71
    private $rows;
72
73
    /**
74
     * @var bool|integer
75
     */
76
    private $rows_before_limit_at_least = false;
77
78
    /**
79
     * @var array
80
     */
81
    private $array_data = [];
82
83
    /**
84
     * @var array
85
     */
86
    private $statistics;
87
88
89 35
    public function __construct(CurlerRequest $request)
90
    {
91 35
        $this->_request = $request;
92 35
        $this->format = $this->_request->getRequestExtendedInfo('format');
93 35
        $this->query = $this->_request->getRequestExtendedInfo('query');
94 35
        $this->sql = $this->_request->getRequestExtendedInfo('sql');
95 35
    }
96
97
    /**
98
     * @return CurlerRequest
99
     */
100
    public function getRequest()
101
    {
102
        return $this->_request;
103
    }
104
105
    /**
106
     * @return CurlerResponse
107
     * @throws Exception\TransportException
108
     */
109 34
    private function response()
110
    {
111 34
        return $this->_request->response();
112
    }
113
114
    /**
115
     * @return mixed
116
     * @throws Exception\TransportException
117
     */
118 1
    public function responseInfo()
119
    {
120 1
        return $this->response()->info();
121
    }
122
123
    /**
124
     * @return mixed|string
125
     */
126 9
    public function sql()
127
    {
128 9
        return $this->sql;
129
    }
130
131
    /**
132
     * @param string $body
133
     * @return array|bool
134
     */
135 5
    private function parseErrorClickHouse($body)
136
    {
137 5
        $body = trim($body);
138 5
        $mathes = [];
139
140
        // Code: 115, e.displayText() = DB::Exception: Unknown setting readonly[0], e.what() = DB::Exception
141
        // Code: 192, e.displayText() = DB::Exception: Unknown user x, e.what() = DB::Exception
142
        // Code: 60, e.displayText() = DB::Exception: Table default.ZZZZZ doesn't exist., e.what() = DB::Exception
143
144 5
        if (preg_match("%Code: (\d+),\se\.displayText\(\) \=\s*DB\:\:Exception\s*:\s*(.*)\,\s*e\.what.*%ius", $body, $mathes)) {
145 5
            return ['code' => $mathes[1], 'message' => $mathes[2]];
146
        }
147
148
        return false;
149
    }
150
151
    /**
152
     * @return bool
153
     * @throws Exception\TransportException
154
     */
155 10
    public function error()
156
    {
157 10
        if (!$this->isError()) {
158 2
            return false;
159
        }
160
161 8
        $body = $this->response()->body();
162 8
        $error_no = $this->response()->error_no();
163 8
        $error=$this->response()->error();
164
165 8
        if (!$error_no && !$error) {
166 5
            $parse = $this->parseErrorClickHouse($body);
167
168 5
            if ($parse) {
169 5
                throw new DatabaseException($parse['message'] . "\nIN:" . $this->sql(), $parse['code']);
170
            }
171
            else {
172
                $code = $this->response()->http_code();
173
                $message = "HttpCode:" . $this->response()->http_code() . " ; ".$this->response()->error()." ;" . $body;
174
            }
175
        }
176
        else {
177 3
            $code = $error_no;
178 3
            $message = $this->response()->error();
179
        }
180
181 3
        throw new QueryException($message, $code);
182
    }
183
184
    /**
185
     * @return bool
186
     * @throws Exception\TransportException
187
     */
188 34
    public function isError()
189
    {
190 34
        return ($this->response()->http_code() !== 200 || $this->response()->error_no());
191
    }
192
193
    /**
194
     * @return bool
195
     * @throws Exception\TransportException
196
     */
197 34
    private function check()
198
    {
199 34
        if (!$this->_request->isResponseExists()) {
200
            throw new QueryException('Not have response');
201
        }
202
203 34
        if ($this->isError()) {
204 3
            $this->error();
205
        }
206
207 34
        return true;
208
    }
209
210
    /**
211
     * @return bool
212
     * @throws Exception\TransportException
213
     */
214 34
    private function init()
215
    {
216 34
        if ($this->_init) {
217
            return false;
218
        }
219
220 34
        $this->check();
221
222
223 34
        $this->_rawData = $this->response()->rawDataOrJson($this->format);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->response()->rawDataOrJson($this->format) can also be of type boolean. However, the property $_rawData is declared as type string. 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...
224
225 34
        if (!$this->_rawData) {
226
            $this->_init = true;
227
            return false;
228
        }
229
230 34
        foreach (['meta', 'data', 'totals', 'extremes', 'rows', 'rows_before_limit_at_least','statistics'] as $key) {
231 34
            if (isset($this->_rawData[$key])) {
232 34
                $this->{$key} = $this->_rawData[$key];
233
            }
234
        }
235
236 34
        if (empty($this->meta)) {
237
            throw  new QueryException('Can`t find meta');
238
        }
239
240 34
        $this->array_data = [];
241 34
        foreach ($this->data as $rows) {
242 34
            $r = [];
243
244 34
            foreach ($this->meta as $meta) {
245 34
                $r[$meta['name']] = $rows[$meta['name']];
246
            }
247
248 34
            $this->array_data[] = $r;
249
        }
250
251 34
        return true;
252
    }
253
254
    /**
255
     * @return array
256
     * @throws \Exception
257
     */
258
    public function extremes()
259
    {
260
        $this->init();
261
        return $this->extremes;
262
    }
263
264
    /**
265
     * @return mixed
266
     * @throws Exception\TransportException
267
     */
268 1
    public function totalTimeRequest()
269
    {
270 1
        $this->check();
271 1
        return $this->response()->total_time();
272
273
    }
274
275
    /**
276
     * @return array
277
     * @throws \Exception
278
     */
279 1
    public function extremesMin()
280
    {
281 1
        $this->init();
282
283 1
        if (empty($this->extremes['min'])) {
284
            return [];
285
        }
286
287 1
        return $this->extremes['min'];
288
    }
289
290
    /**
291
     * @return array
292
     * @throws \Exception
293
     */
294
    public function extremesMax()
295
    {
296
        $this->init();
297
298
        if (empty($this->extremes['max'])) {
299
            return [];
300
        }
301
302
        return $this->extremes['max'];
303
    }
304
305
    /**
306
     * @return array
307
     * @throws Exception\TransportException
308
     */
309 1
    public function totals()
310
    {
311 1
        $this->init();
312 1
        return $this->totals;
313
    }
314
315
    /**
316
     *
317
     */
318
    public function dumpRaw()
319
    {
320
        print_r($this->_rawData);
321
    }
322
323
    /**
324
     *
325
     */
326
    public function dump()
327
    {
328
        $this->_request->dump();
329
        $this->response()->dump();
330
    }
331
332
    /**
333
     * @return bool|int
334
     * @throws Exception\TransportException
335
     */
336 2
    public function countAll()
337
    {
338 2
        $this->init();
339 2
        return $this->rows_before_limit_at_least;
340
    }
341
342
    /**
343
     * @param bool $key
344
     * @return array|mixed|null
345
     * @throws Exception\TransportException
346
     */
347
    public function statistics($key=false)
348
    {
349
        $this->init();
350
        if ($key)
351
        {
352
            if (!is_array($this->statistics)) return null;
0 ignored issues
show
introduced by
The condition is_array($this->statistics) is always true.
Loading history...
353
            if (!isset($this->statistics[$key])) return null;
354
            return $this->statistics[$key];
355
        }
356
        return $this->statistics;
357
    }
358
359
    /**
360
     * @return int
361
     * @throws Exception\TransportException
362
     */
363 6
    public function count()
364
    {
365 6
        $this->init();
366 6
        return $this->rows;
367
    }
368
369
    /**
370
     * @return mixed|string
371
     * @throws Exception\TransportException
372
     */
373 2
    public function rawData()
374
    {
375 2
        if ($this->_init) {
376
            return $this->_rawData;
377
        }
378
379 2
        $this->check();
380
381 2
        return $this->response()->rawDataOrJson($this->format);
382
    }
383
384
    /**
385
     * @param string $key
386
     * @return mixed|null
387
     * @throws Exception\TransportException
388
     */
389 33
    public function fetchOne($key = '')
390
    {
391 33
        $this->init();
392
393 33
        if (isset($this->array_data[0])) {
394 33
            if ($key) {
395 33
                if (isset($this->array_data[0][$key])) {
396 33
                    return $this->array_data[0][$key];
397
                }
398
                else {
399
                    return null;
400
                }
401
            }
402
403 2
            return $this->array_data[0];
404
        }
405
406
        return null;
407
    }
408
409
    /**
410
     * @param string|null $path
411
     * @return array
412
     * @throws Exception\TransportException
413
     */
414 3
    public function rowsAsTree($path)
415
    {
416 3
        $this->init();
417
418 3
        $out = [];
419 3
        foreach ($this->array_data as $row) {
420 3
            $d = $this->array_to_tree($row, $path);
421 3
            $out = array_replace_recursive($d, $out);
422
        }
423
424 3
        return $out;
425
    }
426
427
    /**
428
     * Return size_upload,upload_content,speed_upload,time_request
429
     *
430
     * @return array
431
     * @throws Exception\TransportException
432
     */
433
    public function info_upload()
434
    {
435
        $this->check();
436
        return [
437
            'size_upload'    => $this->response()->size_upload(),
438
            'upload_content' => $this->response()->upload_content_length(),
439
            'speed_upload'   => $this->response()->speed_upload(),
440
            'time_request'   => $this->response()->total_time()
441
        ];
442
    }
443
444
    /**
445
     * Return size_upload,upload_content,speed_upload,time_request,starttransfer_time,size_download,speed_download
446
     *
447
     * @return array
448
     * @throws Exception\TransportException
449
     */
450 1
    public function info()
451
    {
452 1
        $this->check();
453
        return [
454 1
            'starttransfer_time'    => $this->response()->starttransfer_time(),
455 1
            'size_download'    => $this->response()->size_download(),
456 1
            'speed_download'    => $this->response()->speed_download(),
457 1
            'size_upload'    => $this->response()->size_upload(),
458 1
            'upload_content' => $this->response()->upload_content_length(),
459 1
            'speed_upload'   => $this->response()->speed_upload(),
460 1
            'time_request'   => $this->response()->total_time()
461
        ];
462
    }
463
464
    /**
465
     * get format in sql
466
     * @return mixed
467
     */
468 1
    public function getFormat()
469
    {
470 1
        return $this->format;
471
    }
472
473
    /**
474
     * @return array
475
     * @throws Exception\TransportException
476
     */
477 3
    public function rows()
478
    {
479 3
        $this->init();
480 2
        return $this->array_data;
481
    }
482
483
    /**
484
     * @param array|string $arr
485
     * @param null|string|array $path
486
     * @return array
487
     */
488 3
    private function array_to_tree($arr, $path = null)
489
    {
490 3
        if (is_array($path)) {
491
            $keys = $path;
492
        }
493
        else {
494 3
            $args = func_get_args();
495 3
            array_shift($args);
496
497 3
            if (sizeof($args) < 2) {
498 3
                $separator = '.';
499 3
                $keys = explode($separator, $path);
500
            }
501
            else {
502
                $keys = $args;
503
            }
504
        }
505
506
        //
507 3
        $tree = $arr;
508 3
        while (count($keys)) {
509 3
            $key = array_pop($keys);
510
511 3
            if (isset($arr[$key])) {
512 3
                $val = $arr[$key];
513
            }
514
            else {
515
                $val = $key;
516
            }
517
518 3
            $tree = array($val => $tree);
519
        }
520 3
        if (!is_array($tree)) return [];
521 3
        return $tree;
522
    }
523
}
524