Completed
Pull Request — master (#268)
by
unknown
04:24
created

Xhgui_Storage_PDO::getQuery()   F

Complexity

Conditions 27
Paths 10864

Size

Total Lines 158

Duplication

Lines 8
Ratio 5.06 %

Importance

Changes 0
Metric Value
dl 8
loc 158
rs 0
c 0
b 0
f 0
cc 27
nc 10864
nop 2

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * Get profiles using PDO database connection
5
 */
6
class Xhgui_Storage_PDO extends Xhgui_Storage_Abstract implements
7
    \Xhgui_StorageInterface,
8
    \Xhgui_WatchedFunctionsStorageInterface
9
{
10
11
    /**
12
     * @var \PDO
13
     */
14
    protected $connection;
15
16
    /**
17
     * @var bool
18
     */
19
    protected $jsonMode = false;
20
21
    /**
22
     * PDO constructor.
23
     * @param $config
24
     */
25
    public function __construct($config)
26
    {
27
        $this->connection = new \PDO(
28
            $config['db.dsn'],
29
            !empty($config['db.user']) ? $config['db.user'] : null,
30
            !empty($config['db.password']) ? $config['db.password'] : null,
31
            !empty($config['db.options']) ? $config['db.options'] : []
32
        );
33
        $this->connection->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
34
        $this->connection->setAttribute(\PDO::ATTR_EMULATE_PREPARES, false);
35
36
        if (!isset($config['db.json_mode']) && !empty($config['db.dsn'])) {
37
            $this->jsonMode = substr($config['db.dsn'], 0, strpos($config['db.dsn'], ':'));
0 ignored issues
show
Documentation Bug introduced by
The property $jsonMode was declared of type boolean, but substr($config['db.dsn']...config['db.dsn'], ':')) is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
38
        } else {
39
            $this->jsonMode = $config['db.json_mode'];
40
        }
41
    }
42
43
    /**
44
     * @inheritDoc
45
     * @param \Xhgui_Storage_Filter $filter
46
     * @param bool $projections
47
     * @return \Xhgui_Storage_ResultSet
48
     */
49
    public function find(\Xhgui_Storage_Filter $filter, $projections = false)
50
    {
51
        list($query, $params) = $this->getQuery($filter, false);
52
53
        try {
54
            $stmt = $this->connection->prepare($query);
55
            $stmt->execute($params);
56
        } catch (\Exception $e) {
57
            print_r($e->getMessage());
58
            exit;
59
        }
60
61
        $tmp = [];
62
        while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
63
            $meta = json_decode($row['meta'], true);
64 View Code Duplication
            if ($filter->getCookie()) {
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...
65
                // because cookie is not parsed and stored in separate structure we need to double check if
66
                // value that we search is in fact in http cookie server field. SQL filter only checks whole
67
                // meta
68
                if (strpos($meta['SERVER']['HTTP_COOKIE'], $filter->getCookie()) === false) {
69
                    continue;
70
                }
71
            }
72
            $tmp[$row['id']] = $row;
73
            $tmp[$row['id']]['profile'] = json_decode($row['profiles'], true);
74
            $tmp[$row['id']]['meta'] = $meta;
75
        }
76
77
        return new \Xhgui_Storage_ResultSet($tmp);
78
    }
79
80
    /**
81
     * Get query that is used for both list and count
82
     *
83
     * @param \Xhgui_Storage_Filter $filter
84
     * @param bool $count
85
     * @return array
86
     */
87
    protected function getQuery(\Xhgui_Storage_Filter $filter, $count = false)
88
    {
89
        $params = [];
90
91
        if ($count === true) {
92
            $columns = ' count(*) as c ';
93
        } else {
94
            $columns = ' p.*, i.*, m.*, p.profile_id as _id, main_wt as duration ';
95
        }
96
97
        $sql = "
98
select 
99
    $columns
100
from 
101
    profiles as p left join 
102
    profiles_info as i on (p.profile_id = i.id) LEFT JOIN
103
    profiles_meta as m on (p.profile_id = m.profile_id)
104
";
105
106
        $where = [];
107
108
        foreach ([
109
                     'url'         => 'url',
110
                     'method'      => 'method',
111
                     'application' => 'application',
112
                     'version'     => 'version',
113
                     'branch'      => 'branch',
114
                     'controller'  => 'controller',
115
                     'action'      => 'action',
116
                     'cookie'      => 'cookie',
117
                     'remote_addr' => 'ip',
118
                 ] as $dbField => $field) {
119
            $method = 'get' . ucfirst($field);
120
121
            if ($filter->{$method}()) {
122
                switch ($field) {
123
                    case 'url':
124
                        $url = $filter->{$method}();
125
                        $where[] = ' ( url like :url OR simple_url like :simple_url)';
126
                        $params['url'] = '%' . $url . '%';
127
                        $params['simple_url'] = '%' . $url . '%';
128
                        break;
129
130
                    case 'action':
131 View Code Duplication
                    case 'controller':
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...
132
                        $where[] = ' ' . $dbField . ' like :' . $field . ' ';
133
                        $params[$field] = ($filter->{$method}()) . '%';
134
                        break;
135
136
                    case 'cookie':
137
                        // @todo move this to driver specific storage class
138
                        list($where, $params) = $this->compareWithJson(
139
                            $where,
140
                            $params,
141
                            $field,
142
                            $filter->{$method}(),
143
                            'meta',
144
                            ['SERVER', 'HTTP_COOKIE']
145
                        );
146
                        break;
147
148 View Code Duplication
                    default:
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...
149
                        $where[] = ' ' . $dbField . ' = :' . $field . ' ';
150
                        $params[$field] = $filter->{$method}();
151
                        break;
152
                }
153
            }
154
        }
155
156
        if ($filter->getStartDate()) {
157
            $where[] = ' request_time >= :startDate';
158
            $params['startDate'] = $this->getDateTimeFromString($filter->getStartDate(), 'start')
0 ignored issues
show
Documentation introduced by
$filter->getStartDate() is of type object<DateTime>, but the function expects a string|integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
159
                                        ->format('Y-m-d H:i:s');
160
        }
161
162
        if ($filter->getEndDate()) {
163
            $where[] = ' request_time <= :endDate';
164
            $params['endDate'] = $this->getDateTimeFromString($filter->getEndDate(), 'end')
0 ignored issues
show
Documentation introduced by
$filter->getEndDate() is of type object<DateTime>, but the function expects a string|integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
165
                                      ->format('Y-m-d H:i:s');
166
        }
167
168
        if (!empty($where)) {
169
            $sql .= ' WHERE ' . join(' AND ', $where);
170
        }
171
172
        if ($count === true) {
173
            return [$sql, $params];
174
        }
175
176
        switch ($filter->getSort()) {
177
            case 'ct':
178
                $sql .= ' order by main_ct';
179
                break;
180
181
            case 'wt':
182
                $sql .= ' order by main_wt';
183
                break;
184
185
            case 'cpu':
186
                $sql .= ' order by main_cpu';
187
                break;
188
189
            case 'mu':
190
                $sql .= ' order by main_mu';
191
                break;
192
193
            case 'pmu':
194
                $sql .= ' order by main_pmu';
195
                break;
196
197
            case 'controller':
198
                $sql .= ' order by controller';
199
                break;
200
201
            case 'action':
202
                $sql .= ' order by action';
203
                break;
204
205
            case 'application':
206
                $sql .= ' order by application';
207
                break;
208
209
            case 'branch':
210
                $sql .= ' order by branch';
211
                break;
212
213
            case 'version':
214
                $sql .= ' order by version';
215
                break;
216
217
            case 'time':
218
            default:
219
                $sql .= ' order by request_time';
220
                break;
221
        }
222
223
        switch ($filter->getDirection()) {
224
            case 'asc':
225
                $sql .= ' asc ';
226
                break;
227
228
            default:
229
            case 'desc':
0 ignored issues
show
Unused Code introduced by
case 'desc': $sql .= ' desc '; break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
230
                $sql .= ' desc ';
231
                break;
232
        }
233
234
        if ($filter->getPerPage()) {
235
            $sql .= ' LIMIT :limit ';
236
            $params['limit'] = (int)$filter->getPerPage();
237
        }
238
239
        if ($filter->getPage()) {
240
            $sql .= ' OFFSET :offset ';
241
            $params['offset'] = (int)($filter->getPerPage() * ($filter->getPage() - 1));
242
        }
243
        return [$sql, $params];
244
    }
245
246
    /**
247
     * @inheritDoc
248
     * @param Xhgui_Storage_Filter $filter
249
     * @param $col
250
     * @param int $percentile
251
     * @return array
252
     * @throws Exception
253
     */
254
    public function aggregate(\Xhgui_Storage_Filter $filter, $col, $percentile = 1)
255
    {
256
        $stmt = $this->connection->prepare('select 
257
    * 
258
from 
259
    profiles_info
260
where 
261
    simple_url = :simple_url OR url = :url
262
');
263
        $stmt->execute(['url' => $filter->getUrl(), 'simple_url' => $filter->getUrl()]);
264
        $aggregatedData = [];
265
        while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
266
            $date = new \DateTime($row['request_time']);
267
            $formattedDate = $date->format('Y-m-d H:i');
268 View Code Duplication
            if (empty($aggregatedData[$formattedDate])) {
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...
269
                $aggregatedData[$formattedDate] = [
270
                    'wall_times' => [],
271
                    'cpu_times'  => [],
272
                    'mu_times'   => [],
273
                    'pmu_times'  => [],
274
                    'row_count'  => 0
275
                ];
276
            }
277
278
            $aggregatedData[$formattedDate]['wall_times'][] = $row['main_wt'];
279
            $aggregatedData[$formattedDate]['cpu_times'][] = $row['main_cpu'];
280
            $aggregatedData[$formattedDate]['mu_times'][] = $row['main_mu'];
281
            $aggregatedData[$formattedDate]['pmu_times'][] = $row['main_pmu'];
282
            $aggregatedData[$formattedDate]['row_count']++;
283
            $aggregatedData[$formattedDate]['_id'] = $date->format('Y-m-d H:i:s');
284
            $aggregatedData[$formattedDate]['raw_index'] =
285
                $aggregatedData[$formattedDate]['row_count'] * ($percentile / 100);
286
        }
287
288
        $return = [
289
            'ok'     => 1,
290
            'result' => array_values($aggregatedData),
291
        ];
292
        return $return;
293
    }
294
295
    /**
296
     * @inheritDoc
297
     * @@param Xhgui_Storage_Filter $filter
298
     * @return int
299
     */
300
    public function count(\Xhgui_Storage_Filter $filter)
301
    {
302
        list($query, $params) = $this->getQuery($filter, true);
303
        try {
304
            $stmt = $this->connection->prepare($query);
305
            $stmt->execute($params);
306
        } catch (\Exception $e) {
307
            print_r($e->getMessage());
308
            exit;
309
        }
310
311
        $ret = $stmt->fetch(\PDO::FETCH_ASSOC);
312
313
        if (!empty($ret['c'])) {
314
            return $ret['c'];
315
        }
316
        return 0;
317
    }
318
319
    /**
320
     * @inheritDoc
321
     * @param $id
322
     * @return mixed
323
     */
324
    public function findOne($id)
325
    {
326
        $stmt = $this->connection->prepare('
327
select 
328
    * 
329
from 
330
    profiles as p left join 
331
    profiles_info as i on (p.profile_id = i.id) LEFT JOIN
332
    profiles_meta as m on (p.profile_id = m.profile_id)
333
where 
334
    p.profile_id = :id
335
');
336
337
        $stmt->execute(['id' => $id]);
338
        $row = $stmt->fetch(\PDO::FETCH_ASSOC);
339
        $row['profile'] = json_decode($row['profiles'], true);
340
        $row['meta'] = json_decode($row['meta'], true);
341
        $row['_id'] = $id;
342
343
        return $row;
344
    }
345
346
    /**
347
     * @inheritDoc
348
     * @param $id
349
     */
350
    public function remove($id)
351
    {
352
        $this->connection->beginTransaction();
353
        try {
354
            $profileStmt = $this->connection->prepare('delete from profiles where profile_id = :id');
355
            $profileStmt->execute(['id' => $id]);
356
357
            $metaStmt = $this->connection->prepare('delete from profiles_meta where profile_id = :id');
358
            $metaStmt->execute(['id' => $id]);
359
360
            $infoStmt = $this->connection->prepare('delete from profiles_info where id = :id');
361
            $infoStmt->execute(['id' => $id]);
362
363
            $this->connection->commit();
364
        } catch (\Exception $e) {
365
            $this->connection->rollBack();
366
        }
367
    }
368
369
    /**
370
     * @inheritDoc
371
     * Remove all data from profile tables
372
     */
373
    public function drop()
374
    {
375
        $this->connection->exec('delete from profiles');
376
        $this->connection->exec('delete from profiles_meta');
377
        $this->connection->exec('delete from profiles_info');
378
    }
379
380
    /**
381
     * @inheritDoc
382
     * @return array
383
     */
384
    public function getWatchedFunctions()
385
    {
386
        $stmt = $this->connection->query('select * from watched order by name desc');
387
        return $stmt->fetchAll(\PDO::FETCH_ASSOC);
388
    }
389
390
    /**
391
     * @inheritDoc
392
     * @param $name
393
     * @return bool
394
     */
395
    public function addWatchedFunction($name)
396
    {
397
        $name = trim($name);
398
        if (empty($name)) {
399
            return false;
400
        }
401
        $stmt = $this->connection->prepare('INSERT INTO watched (name) VALUES (:name)');
402
        $stmt->execute(['name' => trim($name)]);
403
        return true;
404
    }
405
406
    /**
407
     * @inheritDoc
408
     * @param $id
409
     * @param $name
410
     */
411
    public function updateWatchedFunction($id, $name)
412
    {
413
        $stmt = $this->connection->prepare('update watched set name=:name where id = :id');
414
        $stmt->execute(['id' => $id, 'name' => $name]);
415
    }
416
417
    /**
418
     * @inheritDoc
419
     * @param $id
420
     */
421
    public function removeWatchedFunction($id)
422
    {
423
        $stmt = $this->connection->prepare('delete from watched where id = :id');
424
        $stmt->execute(['id' => $id]);
425
    }
426
427
    /**
428
     * This method will look into json stored data in native way (if db supports that) and it will match row based on
429
     * that.
430
     *
431
     * @param array $where
432
     * @param array $params
433
     * @param $field
434
     * @param $value
435
     * @param $fieldToLookIn
436
     * @param array $path
437
     * @return array
438
     * @todo this should be moved to engine specific storage classes in the future.
439
     */
440
    protected function compareWithJson(array $where, array $params, $field, $value, $fieldToLookIn, array $path)
441
    {
442
        switch ($this->jsonMode) {
443
            case 'mysql':
444
                $where[] = ' JSON_EXTRACT(' . $fieldToLookIn . ", '$." . join('.', $path) . "') like :cookie";
445
                $params[$field] = '%' . $value . '%';
446
                break;
447
448
            case 'pgsql':
449
                // to match using like we need to cast last leaf to a string.
450
                $lastElement = array_pop($path);
451
                $where[] = ' ' . $fieldToLookIn . "->'" . join("'->'", $path)
452
                    . "'->>'" . $lastElement . "' like :cookie";
453
                $params[$field] = '%' . $value . '%';
454
455
                break;
456
            default:
457
                $where[] = ' ' . $fieldToLookIn . ' like :cookie ';
458
                $params[$field] = '%' . $value . '%';
459
                break;
460
        }
461
        return array($where, $params);
462
    }
463
}
464