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

Xhgui_Storage_Mongo   B

Complexity

Total Complexity 47

Size/Duplication

Total Lines 458
Duplicated Lines 11.79 %

Coupling/Cohesion

Components 1
Dependencies 2

Importance

Changes 0
Metric Value
wmc 47
lcom 1
cbo 2
dl 54
loc 458
rs 8.64
c 0
b 0
f 0

20 Methods

Rating   Name   Duplication   Size   Complexity  
A count() 0 7 1
A __construct() 0 16 2
B find() 0 28 9
A findOne() 0 6 1
A remove() 0 7 1
A insert() 0 7 1
A update() 0 8 1
A drop() 0 4 1
A aggregate() 0 51 1
A getWatchedFunctions() 0 15 3
A addWatchedFunction() 24 24 3
A updateWatchedFunction() 21 21 3
A removeWatchedFunction() 0 13 2
A getDateTimeFromString() 0 21 5
B getConditions() 9 35 6
A getMongoClient() 0 7 2
A setMongoClient() 0 4 1
A getConnection() 0 8 2
A setConnection() 0 4 1
A getCollection() 0 4 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_Mongo 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_Mongo, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * Class Xhgui_Storage_Mongo
5
 */
6
class Xhgui_Storage_Mongo implements \Xhgui_StorageInterface, \Xhgui_WatchedFunctionsStorageInterface
7
{
8
9
    protected $config;
10
11
    /**
12
     * @var \MongoDB
13
     */
14
    protected $connection;
15
16
    /**
17
     * @var int
18
     */
19
    protected $defaultPerPage = 25;
20
21
    /**
22
     * @var string
23
     */
24
    protected $collectionName;
25
26
    /**
27
     * @var \MongoClient
28
     */
29
    protected $mongoClient;
30
31
    /**
32
     * Mongo constructor.
33
     * @param $config
34
     * @throws \MongoConnectionException
35
     * @throws \MongoException
36
     */
37
    public function __construct($config, $collection = 'results')
38
    {
39
        $this->config = $config;
40
        // set default number of rows for all results. This can be changed
41
        // for each query
42
        $this->defaultPerPage = $config['page.limit'];
43
44
        $this->collectionName = $collection;
45
46
        // make sure options is an array
47
        if (empty($config['db.options'])) {
48
            $config['db.options'] = array();
49
        }
50
51
        $config['db.options']['connect'] = true;
52
    }
53
54
    /**
55
     * @inheritDoc
56
     * @param $options
57
     * @param bool $projections
58
     * @return Xhgui_Storage_ResultSet
59
     * @throws \MongoCursorException
60
     */
61
    public function find(\Xhgui_Storage_Filter $filter, $projections = false)
62
    {
63
        $sort = [];
64
        switch ($filter->getSort()) {
65
            case 'ct':
66
            case 'wt':
67
            case 'cpu':
68
            case 'mu':
69
            case 'pmu':
70
                $sort['profile.main().' . $filter->getSort()] = $filter->getDirection() === 'asc' ? 1 : -1;
71
                break;
72
            case 'time':
73
                $sort['meta.request_ts'] = $filter->getDirection() === 'asc' ? 1 : -1;
74
                break;
75
        }
76
77
        $conditions = $this->getConditions($filter);
78
79
        $ret = $this->getCollection()
80
                    ->find($conditions)
81
                    ->sort($sort)
82
                    ->skip((int)($filter->getPage() - 1) * $filter->getPerPage())
83
                    ->limit($filter->getPerPage());
84
85
86
        $result = new \Xhgui_Storage_ResultSet(iterator_to_array($ret));
87
        return $result;
88
    }
89
90
    /**
91
     * @inheritDoc
92
     * @param $options
93
     * @return int
94
     * @throws \MongoCursorTimeoutException
95
     * @throws \MongoException
96
     */
97
    public function count(\Xhgui_Storage_Filter $filter)
98
    {
99
        $conditions = $this->getConditions($filter);
100
101
        $ret = $this->getCollection()->find($conditions, array('_id' => 1))->count();
102
        return $ret;
103
    }
104
105
    /**
106
     * @inheritDoc
107
     * @param $id
108
     * @return array|null
109
     * @throws \MongoException
110
     */
111
    public function findOne($id)
112
    {
113
        $ret = $this->getCollection()
114
                    ->findOne(['_id' => new \MongoId($id)]);
115
        return $ret;
116
    }
117
118
    /**
119
     * @inheritDoc
120
     * @param $id
121
     * @return array|bool
122
     * @throws MongoCursorException
123
     * @throws MongoCursorTimeoutException
124
     * @throws MongoException
125
     */
126
    public function remove($id)
127
    {
128
        return $this->getCollection()->remove(
129
            array('_id' => new MongoId($id)),
130
            array('w' => 1)
131
        );
132
    }
133
134
    /**
135
     * @inheritDoc
136
     * @param $data
137
     * @return array|bool
138
     * @throws \MongoCursorException
139
     * @throws \MongoCursorTimeoutException
140
     * @throws \MongoException
141
     */
142
    public function insert(array $data)
143
    {
144
        return $this->getCollection()->insert(
145
            $data,
146
            array('w' => 1)
147
        );
148
    }
149
150
    /**
151
     * @inheritDoc
152
     * @param $id
153
     * @param $data
154
     * @return array|bool
155
     * @throws \MongoCursorException
156
     * @throws \MongoException
157
     * @throws \MongoWriteConcernException
158
     */
159
    public function update($id, array $data)
160
    {
161
        return $this->getCollection()->update(
162
            array('_id' => new MongoId($id)),
163
            $data,
164
            array('w' => 1)
165
        );
166
    }
167
168
    /**
169
     * @inheritDoc
170
     */
171
    public function drop()
172
    {
173
        // TODO: Implement drop() method.
174
    }
175
176
    /**
177
     * @inheritDoc
178
     * @param $match
179
     * @param $col
180
     * @param int $percentile
181
     * @codeCoverageIgnore despite appearances this is very simple function and there is nothing to test here.
182
     * @return array
183
     * @throws \MongoException
184
     */
185
    public function aggregate(\Xhgui_Storage_Filter $filter, $col, $percentile = 1)
186
    {
187
188
        $conditions = $this->getConditions($filter);
189
        $param = [
190
            ['$match' => $conditions],
191
            [
192
                '$project' => [
193
                    'date'           => '$meta.request_ts',
194
                    'profile.main()' => 1
195
                ]
196
            ],
197
            [
198
                '$group' => [
199
                    '_id'        => '$date',
200
                    'row_count'  => ['$sum' => 1],
201
                    'wall_times' => ['$push' => '$profile.main().wt'],
202
                    'cpu_times'  => ['$push' => '$profile.main().cpu'],
203
                    'mu_times'   => ['$push' => '$profile.main().mu'],
204
                    'pmu_times'  => ['$push' => '$profile.main().pmu'],
205
                ]
206
            ],
207
            [
208
                '$project' => [
209
                    'date'       => '$date',
210
                    'row_count'  => '$row_count',
211
                    'raw_index'  => [
212
                        '$multiply' => [
213
                            '$row_count',
214
                            $percentile / 100
215
                        ]
216
                    ],
217
                    'wall_times' => '$wall_times',
218
                    'cpu_times'  => '$cpu_times',
219
                    'mu_times'   => '$mu_times',
220
                    'pmu_times'  => '$pmu_times',
221
                ]
222
            ],
223
            [
224
                '$sort' => ['_id' => 1]
225
            ],
226
        ];
227
        $ret = $this->getCollection()->aggregate(
228
            $param,
229
            [
230
                'cursor' => ['batchSize' => 0]
231
            ]
232
        );
233
234
        return $ret;
235
    }
236
237
    /**
238
     * @inheritDoc
239
     * @return array
240
     */
241
    public function getWatchedFunctions()
242
    {
243
        $ret = [];
244
        try {
245
            $cursor = $this->getConnection()->watches->find()->sort(['name' => 1]);
246
            $ret = [];
247
            foreach ($cursor as $row) {
248
                $ret[] = ['id' => $row['_id']->__toString(), 'name' => $row['name']];
249
            }
250
        } catch (\Exception $e) {
251
            // if something goes wrong just return empty array
252
            // @todo add exception
253
        }
254
        return $ret;
255
    }
256
257
    /**
258
     * @inheritDoc
259
     * @param $name
260
     * @return bool
261
     */
262 View Code Duplication
    public function addWatchedFunction($name)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
263
    {
264
265
        $name = trim($name);
266
        if (empty($name)) {
267
            return false;
268
        }
269
270
        try {
271
            $id = new \MongoId();
272
273
            $data = [
274
                '_id'  => $id,
275
                'name' => $name
276
            ];
277
            $this->getConnection()->watches->insert($data);
278
279
            return true;
280
        } catch (\Exception $e) {
281
            // if something goes wrong just ignore for now
282
            // @todo add exception
283
        }
284
        return false;
285
    }
286
287
    /**
288
     * @inheritDoc
289
     * @param $id
290
     * @param $name
291
     * @return bool
292
     */
293 View Code Duplication
    public function updateWatchedFunction($id, $name)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
294
    {
295
        $name = trim($name);
296
        if (empty($name)) {
297
            return false;
298
        }
299
300
        try {
301
            $id = new \MongoId($id);
302
            $data = [
303
                '_id'  => $id,
304
                'name' => $name
305
            ];
306
            $this->getConnection()->watches->save($data);
307
308
            return true;
309
        } catch (\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
310
        }
311
312
        return false;
313
    }
314
315
    /**
316
     * @inheritDoc
317
     * @param $id
318
     * @return bool
319
     */
320
    public function removeWatchedFunction($id)
321
    {
322
323
        try {
324
            $id = new \MongoId($id);
325
326
            $this->getConnection()->watches->remove(['_id' => $id]);
327
328
            return true;
329
        } catch (\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
330
        }
331
        return false;
332
    }
333
334
    /**
335
     * Try to parse given string as a datetime or unix timestamp
336
     *
337
     * @param string|int $date
338
     * @return \DateTime
339
     */
340
    protected function getDateTimeFromString($date)
341
    {
342
        try {
343
            $parsedDate = \DateTime::createFromFormat('Y-m-d H:i:s', $date);
344
            if (!empty($parsedDate)) {
345
                return $parsedDate;
346
            }
347
        } catch (\Exception $e) {
348
            // leave empty to try parse different format below
349
        }
350
351
        try {
352
            $parsedDate = \DateTime::createFromFormat('U', $date);
353
            if (!empty($parsedDate)) {
354
                return $parsedDate;
355
            }
356
        } catch (\Exception $e) {
357
            // throw generic exception on failure
358
        }
359
        throw new \InvalidArgumentException('Unable to parse date');
360
    }
361
362
    /**
363
     * Convert filter into mongo condition
364
     *
365
     * @param \Xhgui_Storage_Filter $filter
366
     * @return array
367
     */
368
    protected function getConditions(\Xhgui_Storage_Filter $filter)
369
    {
370
        $conditions = [];
371 View Code Duplication
        if (null !== $filter->getStartDate()) {
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...
372
            $conditions['meta.request_ts']['$gte'] = new \MongoDate(
373
                $this->getDateTimeFromString($filter->getStartDate())->format('U')
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...
374
            );
375
        }
376
377 View Code Duplication
        if (null !== $filter->getEndDate()) {
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...
378
            $conditions['meta.request_ts']['$lte'] = new \MongoDate(
379
                $this->getDateTimeFromString($filter->getEndDate())->format('U')
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...
380
            );
381
        }
382
383
        if (null !== $filter->getUrl()) {
384
            $conditions['meta.simple_url'] = $filter->getUrl();
385
        }
386
387
        foreach ([
388
                     'method'      => 'method',
389
                     'application' => 'application',
390
                     'version'     => 'version',
391
                     'branch'      => 'branch',
392
                     'controller'  => 'controller',
393
                     'action'      => 'action',
394
                 ] as $dbField => $field) {
395
            $method = 'get' . ucfirst($field);
396
            if ($filter->{$method}()) {
397
                $conditions['meta.' . $dbField] = $filter->{$method}();
398
            }
399
        }
400
401
        return $conditions;
402
    }
403
404
    /**
405
     * Get mongo client from config
406
     *
407
     * @return MongoClient
408
     * @throws MongoConnectionException
409
     */
410
    public function getMongoClient()
411
    {
412
        if (empty($this->mongoClient)) {
413
            $this->mongoClient = new \MongoClient($this->config['db.host'], $this->config['db.options']);
414
        }
415
        return $this->mongoClient;
416
    }
417
418
    /**
419
     * Set prepared mongo client.
420
     *
421
     * @param MongoClient $mongoClient
422
     */
423
    public function setMongoClient($mongoClient)
424
    {
425
        $this->mongoClient = $mongoClient;
426
    }
427
428
    /**
429
     * Get connection.
430
     *
431
     * @return MongoDB
432
     * @throws MongoConnectionException
433
     */
434
    public function getConnection()
435
    {
436
        if (empty($this->connection)) {
437
            $this->connection = $this->getMongoClient()->{$this->config['db.db']};
438
        }
439
440
        return $this->connection;
441
    }
442
443
    /**
444
     * Set existing connection
445
     *
446
     * @param MongoDB $connection
447
     */
448
    public function setConnection($connection)
449
    {
450
        $this->connection = $connection;
451
    }
452
453
    /**
454
     * Select specific connection
455
     *
456
     * @return MongoCollection
457
     * @throws Exception
458
     */
459
    public function getCollection()
460
    {
461
        return $this->getConnection()->selectCollection($this->collectionName);
462
    }
463
}
464