Completed
Pull Request — master (#268)
by
unknown
02:06 queued 55s
created

Xhgui_Storage_Mongo::find()   B

Complexity

Conditions 9
Paths 13

Size

Total Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 28
rs 8.0555
c 0
b 0
f 0
cc 9
nc 13
nop 2
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
     * @param $data
139
     * @return array|bool
140
     * @throws \MongoCursorException
141
     * @throws \MongoCursorTimeoutException
142
     * @throws \MongoException
143
     */
144
    public function insert(array $data)
145
    {
146
        return $this->getCollection()->insert(
147
            $data,
148
            array('w' => 1)
149
        );
150
    }
151
152
    /**
153
     * @inheritDoc
154
     * @param $id
155
     * @param $data
156
     * @return array|bool
157
     * @throws \MongoCursorException
158
     * @throws \MongoException
159
     * @throws \MongoWriteConcernException
160
     */
161
    public function update($id, array $data)
162
    {
163
        return $this->getCollection()->update(
164
            array('_id' => new MongoId($id)),
165
            $data,
166
            array('w' => 1)
167
        );
168
    }
169
170
    /**
171
     * @inheritDoc
172
     */
173
    public function drop()
174
    {
175
        // TODO: Implement drop() method.
176
    }
177
178
    /**
179
     * @inheritDoc
180
     * @param $match
181
     * @param $col
182
     * @param int $percentile
183
     * @codeCoverageIgnore despite appearances this is very simple function and there is nothing to test here.
184
     * @return array
185
     * @throws \MongoException
186
     */
187
    public function aggregate(\Xhgui_Storage_Filter $filter, $col, $percentile = 1)
188
    {
189
190
        $conditions = $this->getConditions($filter);
191
        $param = [
192
            ['$match' => $conditions],
193
            [
194
                '$project' => [
195
                    'date'           => '$meta.request_ts',
196
                    'profile.main()' => 1
197
                ]
198
            ],
199
            [
200
                '$group' => [
201
                    '_id'        => '$date',
202
                    'row_count'  => ['$sum' => 1],
203
                    'wall_times' => ['$push' => '$profile.main().wt'],
204
                    'cpu_times'  => ['$push' => '$profile.main().cpu'],
205
                    'mu_times'   => ['$push' => '$profile.main().mu'],
206
                    'pmu_times'  => ['$push' => '$profile.main().pmu'],
207
                ]
208
            ],
209
            [
210
                '$project' => [
211
                    'date'       => '$date',
212
                    'row_count'  => '$row_count',
213
                    'raw_index'  => [
214
                        '$multiply' => [
215
                            '$row_count',
216
                            $percentile / 100
217
                        ]
218
                    ],
219
                    'wall_times' => '$wall_times',
220
                    'cpu_times'  => '$cpu_times',
221
                    'mu_times'   => '$mu_times',
222
                    'pmu_times'  => '$pmu_times',
223
                ]
224
            ],
225
            [
226
                '$sort' => ['_id' => 1]
227
            ],
228
        ];
229
        $ret = $this->getCollection()->aggregate(
230
            $param,
231
            [
232
                'cursor' => ['batchSize' => 0]
233
            ]
234
        );
235
236
        return $ret;
237
    }
238
239
    /**
240
     * @inheritDoc
241
     * @return array
242
     */
243
    public function getWatchedFunctions()
244
    {
245
        $ret = [];
246
        try {
247
            $cursor = $this->getConnection()->watches->find()->sort(['name' => 1]);
248
            $ret = [];
249
            foreach ($cursor as $row) {
250
                $ret[] = ['id' => $row['_id']->__toString(), 'name' => $row['name']];
251
            }
252
        } catch (\Exception $e) {
253
            // if something goes wrong just return empty array
254
            // @todo add exception
255
        }
256
        return $ret;
257
    }
258
259
    /**
260
     * @inheritDoc
261
     * @param $name
262
     * @return bool
263
     */
264 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...
265
    {
266
267
        $name = trim($name);
268
        if (empty($name)) {
269
            return false;
270
        }
271
272
        try {
273
            $id = new \MongoId();
274
275
            $data = [
276
                '_id'  => $id,
277
                'name' => $name
278
            ];
279
            $this->getConnection()->watches->insert($data);
280
281
            return true;
282
        } catch (\Exception $e) {
283
            // if something goes wrong just ignore for now
284
            // @todo add exception
285
        }
286
        return false;
287
    }
288
289
    /**
290
     * @inheritDoc
291
     * @param $id
292
     * @param $name
293
     * @return bool
294
     */
295 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...
296
    {
297
        $name = trim($name);
298
        if (empty($name)) {
299
            return false;
300
        }
301
302
        try {
303
            $id = new \MongoId($id);
304
            $data = [
305
                '_id'  => $id,
306
                'name' => $name
307
            ];
308
            $this->getConnection()->watches->save($data);
309
310
            return true;
311
        } catch (\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
312
        }
313
314
        return false;
315
    }
316
317
    /**
318
     * @inheritDoc
319
     * @param $id
320
     * @return bool
321
     */
322
    public function removeWatchedFunction($id)
323
    {
324
325
        try {
326
            $id = new \MongoId($id);
327
328
            $this->getConnection()->watches->remove(['_id' => $id]);
329
330
            return true;
331
        } catch (\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
332
        }
333
        return false;
334
    }
335
336
    /**
337
     * Convert filter into mongo condition
338
     *
339
     * @param \Xhgui_Storage_Filter $filter
340
     * @return array
341
     */
342
    protected function getConditions(\Xhgui_Storage_Filter $filter)
343
    {
344
        $conditions = [];
345 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...
346
            $conditions['meta.request_ts']['$gte'] = new \MongoDate(
347
                $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...
348
            );
349
        }
350
351 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...
352
            $conditions['meta.request_ts']['$lte'] = new \MongoDate(
353
                $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...
354
            );
355
        }
356
357
        if (null !== $filter->getUrl()) {
358
            $conditions['meta.simple_url'] = $filter->getUrl();
359
        }
360
361
        foreach ([
362
                     'method'      => 'method',
363
                     'application' => 'application',
364
                     'version'     => 'version',
365
                     'branch'      => 'branch',
366
                     'controller'  => 'controller',
367
                     'action'      => 'action',
368
                 ] as $dbField => $field) {
369
            $method = 'get' . ucfirst($field);
370
            if ($filter->{$method}()) {
371
                $conditions['meta.' . $dbField] = $filter->{$method}();
372
            }
373
        }
374
375
        return $conditions;
376
    }
377
378
    /**
379
     * Get mongo client from config
380
     *
381
     * @return MongoClient
382
     * @throws MongoConnectionException
383
     */
384
    public function getMongoClient()
385
    {
386
        if (empty($this->mongoClient)) {
387
            $this->mongoClient = new \MongoClient($this->config['db.host'], $this->config['db.options']);
388
        }
389
        return $this->mongoClient;
390
    }
391
392
    /**
393
     * Set prepared mongo client.
394
     *
395
     * @param MongoClient $mongoClient
396
     */
397
    public function setMongoClient($mongoClient)
398
    {
399
        $this->mongoClient = $mongoClient;
400
    }
401
402
    /**
403
     * Get connection.
404
     *
405
     * @return MongoDB
406
     * @throws MongoConnectionException
407
     */
408
    public function getConnection()
409
    {
410
        if (empty($this->connection)) {
411
            $this->connection = $this->getMongoClient()->{$this->config['db.db']};
412
        }
413
414
        return $this->connection;
415
    }
416
417
    /**
418
     * Set existing connection
419
     *
420
     * @param MongoDB $connection
421
     */
422
    public function setConnection($connection)
423
    {
424
        $this->connection = $connection;
425
    }
426
427
    /**
428
     * Select specific connection
429
     *
430
     * @return MongoCollection
431
     * @throws Exception
432
     */
433
    public function getCollection()
434
    {
435
        return $this->getConnection()->selectCollection($this->collectionName);
436
    }
437
}
438