Completed
Pull Request — master (#268)
by
unknown
03:19
created

Xhgui_Storage_PDO   B

Complexity

Total Complexity 52

Size/Duplication

Total Lines 412
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Importance

Changes 0
Metric Value
wmc 52
lcom 1
cbo 2
dl 0
loc 412
rs 7.44
c 0
b 0
f 0

15 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 10 4
A find() 0 21 3
F getQuery() 0 125 23
A aggregate() 0 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
A getDateTimeFromString() 0 21 5
A insert() 0 4 1
A update() 0 4 1

How to fix   Complexity   

Complex Class

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