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

Xhgui_Storage_Mongo   B

Complexity

Total Complexity 47

Size/Duplication

Total Lines 433
Duplicated Lines 12.47 %

Coupling/Cohesion

Components 1
Dependencies 2

Importance

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

20 Methods

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