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

Xhgui_Storage_PDO   B

Complexity

Total Complexity 51

Size/Duplication

Total Lines 403
Duplicated Lines 4.22 %

Coupling/Cohesion

Components 1
Dependencies 3

Importance

Changes 0
Metric Value
wmc 51
lcom 1
cbo 3
dl 17
loc 403
rs 7.92
c 0
b 0
f 0

12 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 11 4
A find() 0 30 5
F getQuery() 8 151 27
A aggregate() 9 40 3
A count() 0 18 3
A findOne() 0 21 1
A remove() 0 18 2
A drop() 0 6 1
A getWatchedFunctions() 0 5 1
A addWatchedFunction() 0 10 2
A updateWatchedFunction() 0 5 1
A removeWatchedFunction() 0 5 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 Xhgui_Storage_PDO 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 Xhgui_Storage_PDO, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Get profiles using PDO database connection
4
 */
5
class Xhgui_Storage_PDO extends Xhgui_Storage_Abstract implements
6
    \Xhgui_StorageInterface,
7
    \Xhgui_WatchedFunctionsStorageInterface
8
{
9
10
    /**
11
     * @var \PDO
12
     */
13
    protected $connection;
14
15
    /**
16
     * PDO constructor.
17
     * @param $config
18
     */
19
    public function __construct($config)
20
    {
21
        $this->connection = new \PDO(
22
            $config['db.dsn'],
23
            !empty($config['db.user'])      ? $config['db.user'] : null,
24
            !empty($config['db.password'])  ? $config['db.password'] : null,
25
            !empty($config['db.options'])   ? $config['db.options'] : []
26
        );
27
        $this->connection->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
28
        $this->connection->setAttribute(\PDO::ATTR_EMULATE_PREPARES, false);
29
    }
30
31
    /**
32
     * @inheritDoc
33
     * @param \Xhgui_Storage_Filter $filter
34
     * @param bool $projections
35
     * @return \Xhgui_Storage_ResultSet
36
     */
37
    public function find(\Xhgui_Storage_Filter $filter, $projections = false)
38
    {
39
        list($query, $params) = $this->getQuery($filter, false);
40
        
41
        try {
42
            $stmt = $this->connection->prepare($query);
43
            $stmt->execute($params);
44
        } catch (\Exception $e) {
45
            print_r($e->getMessage());
46
            exit;
47
        }
48
49
        $tmp = [];
50
        while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
51
            $meta = json_decode($row['meta'], true);
52
            if ($filter->getCookie()) {
53
                // because cookie is not parsed and stored in separate structure we need to double check if
54
                // value that we search is in fact in http cookie server field. SQL filter only checks whole
55
                // meta
56
                if (strpos($meta['SERVER']['HTTP_COOKIE'], $filter->getCookie()) === false) {
57
                    continue;
58
                }
59
            }
60
            $tmp[$row['id']] = $row;
61
            $tmp[$row['id']]['profile']    = json_decode($row['profiles'], true);
62
            $tmp[$row['id']]['meta']       = $meta;
63
        }
64
        
65
        return new \Xhgui_Storage_ResultSet($tmp);
66
    }
67
68
    /**
69
     * Get query that is used for both list and count
70
     *
71
     * @param \Xhgui_Storage_Filter $filter
72
     * @param bool $count
73
     * @return array
74
     */
75
    protected function getQuery(\Xhgui_Storage_Filter $filter, $count = false)
76
    {
77
        $params = [];
78
79
        if ($count === true) {
80
            $columns = ' count(*) as c ';
81
        } else {
82
            $columns = ' p.*, i.*, m.*, p.profile_id as _id, main_wt as duration ';
83
        }
84
85
        $sql = "
86
select 
87
    $columns
88
from 
89
    profiles as p left join 
90
    profiles_info as i on (p.profile_id = i.id) LEFT JOIN
91
    profiles_meta as m on (p.profile_id = m.profile_id)
92
";
93
94
        $where = [];
95
96
        foreach ([
97
            'url'               => 'url',
98
            'method'            => 'method',
99
            'application'       => 'application',
100
            'version'           => 'version',
101
            'branch'            => 'branch',
102
            'controller'        => 'controller',
103
            'action'            => 'action',
104
            'cookie'            => 'cookie',
105
            ] as $dbField => $field) {
106
            $method = 'get'.ucfirst($field);
107
108
            if ($filter->{$method}()) {
109
                switch($field) {
110
                    case 'url':
111
                        $url = $filter->{$method}();
112
                        $where[]              = ' ( url like :url OR simple_url like :simple_url)';
113
                        $params['url']        = '%'.$url.'%';
114
                        $params['simple_url'] = '%'.$url.'%';
115
                        break;
116
117
                    case 'action':
118 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...
119
                        $where[]        = ' '.$dbField.' like :'.$field.' ';
120
                        $params[$field] = ($filter->{$method}()).'%';
121
                        break;
122
123
                    case 'cookie':
124
                        $where[]        = ' meta like :cookie ';
125
                        $params[$field] = '%'.($filter->{$method}()).'%';
126
127
                        break;
128
129 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...
130
                        $where[]        = ' '.$dbField.' = :'.$field.' ';
131
                        $params[$field] = $filter->{$method}();
132
                        break;
133
                }
134
            }
135
        }
136
        
137
        if ($filter->getStartDate()) {
138
            $where[]                = ' request_time >= :startDate';
139
            $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...
140
                                          ->format('Y-m-d H:i:s');
141
        }
142
143
        if ($filter->getEndDate()) {
144
            $where[]                = ' request_time <= :endDate';
145
            $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...
146
                                        ->format('Y-m-d H:i:s');
147
        }
148
149
        if (!empty($where)) {
150
            $sql .= ' WHERE '.join(' AND ', $where);
151
        }
152
153
        if ($count === true) {
154
            return [$sql, $params];
155
        }
156
157
        switch ($filter->getSort()) {
158
            case 'ct':
159
                $sql .= ' order by main_ct';
160
                break;
161
162
            case 'wt':
163
                $sql .= ' order by main_wt';
164
                break;
165
166
            case 'cpu':
167
                $sql .= ' order by main_cpu';
168
                break;
169
170
            case 'mu':
171
                $sql .= ' order by main_mu';
172
                break;
173
174
            case 'pmu':
175
                $sql .= ' order by main_pmu';
176
                break;
177
178
            case 'controller':
179
                $sql .= ' order by controller';
180
                break;
181
182
            case 'action':
183
                $sql .= ' order by action';
184
                break;
185
186
            case 'application':
187
                $sql .= ' order by application';
188
                break;
189
190
            case 'branch':
191
                $sql .= ' order by branch';
192
                break;
193
194
            case 'version':
195
                $sql .= ' order by version';
196
                break;
197
198
            case 'time':
199
            default:
200
                $sql .= ' order by request_time';
201
                break;
202
        }
203
204
        switch ($filter->getDirection()) {
205
            case 'asc':
206
                $sql .= ' asc ';
207
                break;
208
209
            default:
210
            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...
211
                $sql .= ' desc ';
212
                break;
213
        }
214
215
        if ($filter->getPerPage()) {
216
            $sql            .= ' LIMIT :limit ';
217
            $params['limit'] = (int)$filter->getPerPage();
218
        }
219
220
        if ($filter->getPage()) {
221
            $sql                .= ' OFFSET :offset ';
222
            $params['offset']   = (int)($filter->getPerPage()*($filter->getPage()-1));
223
        }
224
        return [$sql, $params];
225
    }
226
227
    /**
228
     * @inheritDoc
229
     * @param Xhgui_Storage_Filter $filter
230
     * @param $col
231
     * @param int $percentile
232
     * @return array
233
     * @throws Exception
234
     */
235
    public function aggregate(\Xhgui_Storage_Filter $filter, $col, $percentile = 1)
236
    {
237
        $stmt = $this->connection->prepare('select 
238
    * 
239
from 
240
    profiles_info
241
where 
242
    simple_url = :simple_url OR url = :url
243
');
244
        $stmt->execute(['url'=> $filter->getUrl(), 'simple_url'=> $filter->getUrl()]);
245
        $aggregatedData = [];
246
        while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
247
            $date = new \DateTime($row['request_time']);
248
            $formattedDate = $date->format('Y-m-d H:i');
249 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...
250
                $aggregatedData[$formattedDate] = [
251
                    'wall_times'    => [],
252
                    'cpu_times'     => [],
253
                    'mu_times'      => [],
254
                    'pmu_times'     => [],
255
                    'row_count'     => 0
256
                ];
257
            }
258
259
            $aggregatedData[$formattedDate]['wall_times'][] = $row['main_wt'];
260
            $aggregatedData[$formattedDate]['cpu_times'][]  = $row['main_cpu'];
261
            $aggregatedData[$formattedDate]['mu_times'][]   = $row['main_mu'];
262
            $aggregatedData[$formattedDate]['pmu_times'][]  = $row['main_pmu'];
263
            $aggregatedData[$formattedDate]['row_count']++;
264
            $aggregatedData[$formattedDate]['_id']          = $date->format('Y-m-d H:i:s');
265
            $aggregatedData[$formattedDate]['raw_index']    =
266
                $aggregatedData[$formattedDate]['row_count']*($percentile/100);
267
        }
268
269
        $return = [
270
            'ok'    => 1,
271
            'result'=> array_values($aggregatedData),
272
        ];
273
        return $return;
274
    }
275
276
    /**
277
     * @inheritDoc
278
     * @@param Xhgui_Storage_Filter $filter
279
     * @return int
280
     */
281
    public function count(\Xhgui_Storage_Filter $filter)
282
    {
283
        list($query, $params) = $this->getQuery($filter, true);
284
        try {
285
            $stmt = $this->connection->prepare($query);
286
            $stmt->execute($params);
287
        } catch (\Exception $e) {
288
            print_r($e->getMessage());
289
            exit;
290
        }
291
292
        $ret = $stmt->fetch(\PDO::FETCH_ASSOC);
293
294
        if (!empty($ret['c'])) {
295
            return $ret['c'];
296
        }
297
        return 0;
298
    }
299
300
    /**
301
     * @inheritDoc
302
     * @param $id
303
     * @return mixed
304
     */
305
    public function findOne($id)
306
    {
307
        $stmt = $this->connection->prepare('
308
select 
309
    * 
310
from 
311
    profiles as p left join 
312
    profiles_info as i on (p.profile_id = i.id) LEFT JOIN
313
    profiles_meta as m on (p.profile_id = m.profile_id)
314
where 
315
    p.profile_id = :id
316
');
317
318
        $stmt->execute(['id'=>$id]);
319
        $row = $stmt->fetch(\PDO::FETCH_ASSOC);
320
        $row['profile'] = json_decode($row['profiles'], true);
321
        $row['meta']    = json_decode($row['meta'], true);
322
        $row['_id']     = $id;
323
324
        return $row;
325
    }
326
327
    /**
328
     * @inheritDoc
329
     * @param $id
330
     */
331
    public function remove($id)
332
    {
333
        $this->connection->beginTransaction();
334
        try {
335
            $profileStmt = $this->connection->prepare('delete from profiles where profile_id = :id');
336
            $profileStmt->execute(['id'=>$id]);
337
338
            $metaStmt = $this->connection->prepare('delete from profiles_meta where profile_id = :id');
339
            $metaStmt->execute(['id'=>$id]);
340
341
            $infoStmt = $this->connection->prepare('delete from profiles_info where id = :id');
342
            $infoStmt->execute(['id'=>$id]);
343
            
344
            $this->connection->commit();
345
        } catch (\Exception $e) {
346
            $this->connection->rollBack();
347
        }
348
    }
349
350
    /**
351
     * @inheritDoc
352
     * Remove all data from profile tables
353
     */
354
    public function drop()
355
    {
356
        $this->connection->exec('delete from profiles');
357
        $this->connection->exec('delete from profiles_meta');
358
        $this->connection->exec('delete from profiles_info');
359
    }
360
361
    /**
362
     * @inheritDoc
363
     * @return array
364
     */
365
    public function getWatchedFunctions()
366
    {
367
        $stmt = $this->connection->query('select * from watched order by name desc');
368
        return $stmt->fetchAll(\PDO::FETCH_ASSOC);
369
    }
370
371
    /**
372
     * @inheritDoc
373
     * @param $name
374
     * @return bool
375
     */
376
    public function addWatchedFunction($name)
377
    {
378
        $name = trim($name);
379
        if (empty($name)) {
380
            return false;
381
        }
382
        $stmt = $this->connection->prepare('INSERT INTO watched (name) VALUES (:name)');
383
        $stmt->execute(['name'=>trim($name)]);
384
        return true;
385
    }
386
387
    /**
388
     * @inheritDoc
389
     * @param $id
390
     * @param $name
391
     */
392
    public function updateWatchedFunction($id, $name)
393
    {
394
        $stmt = $this->connection->prepare('update watched set name=:name where id = :id');
395
        $stmt->execute(['id'=>$id, 'name'=>$name]);
396
    }
397
398
    /**
399
     * @inheritDoc
400
     * @param $id
401
     */
402
    public function removeWatchedFunction($id)
403
    {
404
        $stmt = $this->connection->prepare('delete from watched where id = :id');
405
        $stmt->execute(['id'=>$id]);
406
    }
407
}
408