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