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

Xhgui_Storage_Mongo   A

Complexity

Total Complexity 42

Size/Duplication

Total Lines 406
Duplicated Lines 15.02 %

Coupling/Cohesion

Components 1
Dependencies 3

Importance

Changes 0
Metric Value
wmc 42
lcom 1
cbo 3
dl 61
loc 406
rs 9.0399
c 0
b 0
f 0

17 Methods

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