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

Xhgui_Storage_File::find()   F

Complexity

Conditions 32
Paths 832

Size

Total Lines 113

Duplication

Lines 3
Ratio 2.65 %

Importance

Changes 0
Metric Value
dl 3
loc 113
rs 0.1865
c 0
b 0
f 0
cc 32
nc 832
nop 2

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
class Xhgui_Storage_File extends Xhgui_Storage_Abstract implements
4
    Xhgui_StorageInterface,
5
    Xhgui_WatchedFunctionsStorageInterface
6
{
7
8
    /**
9
     * @var string
10
     */
11
    protected $path = '../data/';
12
13
    /**
14
     * @var string
15
     */
16
    protected $prefix = 'xhgui.data';
17
18
    /**
19
     * @var bool|mixed
20
     */
21
    protected $separateMeta = true;
22
23
    /**
24
     * @var mixed
25
     */
26
    protected $dataSerializer;
27
28
    /**
29
     * @var mixed
30
     */
31
    protected $metaSerializer;
32
33
    /**
34
     * @var string
35
     */
36
    protected $watchedFunctionsPathPrefix = '../watched_functions/';
37
38
    /**
39
     * @var int[]
40
     */
41
    protected $countCache;
42
43
    /**
44
     * @var Xhgui_Storage_Filter
45
     */
46
    private $filter;
47
48
    /**
49
     * Xhgui_Storage_File constructor.
50
     * @param $config
51
     */
52
    public function __construct($config)
53
    {
54
        $this->prefix = 'xhgui.data';
55
56
        $this->path = $config['save.handler.path'];
57
        $this->separateMeta = $config['save.handler.separate_meta'];
58
        $this->dataSerializer = $config['save.handler.serializer'];
59
        $this->metaSerializer = $config['save.handler.meta_serializer'];
60
    }
61
62
    /**
63
     * @inheritDoc
64
     * @param Xhgui_Storage_Filter $filter
65
     * @param bool $projections
66
     * @return Xhgui_Storage_ResultSet
67
     */
68
    public function find(Xhgui_Storage_Filter $filter, $projections = false)
69
    {
70
71
        if ($filter->getId()) {
72
            $result = glob($this->path . DIRECTORY_SEPARATOR . $filter->getId());
73
        } else {
74
            $result = glob($this->path . $this->prefix . '*');
75
            sort($result);
76
        }
77
78
        $ret = [];
79
        foreach ($result as $i => $file) {
80
            // skip meta files.
81
            if (strpos($file, '.meta') !== false) {
82
                continue;
83
            }
84
85
            // try to detect timestamp in filename, to optimize searching.
86
            // If that fails we need to get it after file import from meta.
87
            $reqTimeFromFilename = $this->getRequestTimeFromFilename($file);
88
            if (!empty($reqTimeFromFilename)) {
89
                if (null !== $filter->getStartDate() &&
90
                    $this->getDateTimeFromString($filter->getStartDate(), 'start') >= $reqTimeFromFilename) {
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...
91
                    continue;
92
                }
93
94
                if (null !== $filter->getEndDate() &&
95
                    $this->getDateTimeFromString($filter->getEndDate(), 'end') <= $reqTimeFromFilename) {
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...
96
                    continue;
97
                }
98
            }
99
100
            $metaFile = $this->getMetafileNameFromProfileName($file);
101
102
            $meta = $this->importFile($metaFile, true);
103
            if ($meta === false) {
104
                continue;
105
            }
106
107
            $profile = $this->importFile($file, false);
108
            if ($profile === false) {
109
                continue;
110
            }
111
112
            if (!empty($profile['meta'])) {
113
                $meta = array_merge($meta, $profile['meta']);
114
            }
115
116
            if (empty($reqTimeFromFilename) && (null !== $filter->getStartDate() || null !== $filter->getEndDate())) {
117
                if (null !== $filter->getStartDate() &&
118
                    $this->getDateTimeFromString($filter->getStartDate(), 'start') >= $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...
119
                    continue;
120
                }
121
                if (null !== $filter->getEndDate() &&
122
                    $this->getDateTimeFromString($filter->getEndDate(), 'end') <= $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...
123
                    continue;
124
                }
125
            }
126
127
            if ($filter->getUrl() &&
128
                strpos($meta['simple_url'], $filter->getUrl()) === false &&
129
                strpos($meta['SERVER']['SERVER_NAME'] . $meta['simple_url'], $filter->getUrl()) === false
130
            ) {
131
                continue;
132
            }
133
134 View Code Duplication
            if (null !== $filter->getCookie() && strpos($meta['SERVER']['HTTP_COOKIE'],
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...
135
                    $filter->getCookie()) === false) {
136
                continue;
137
            }
138
139
            if (null !== $filter->getIp() && $meta['SERVER']['REMOTE_ADDR'] !== $filter->getIp()) {
140
                continue;
141
            }
142
143
            if (!empty($profile['profile'])) {
144
                $profile = $profile['profile'];
145
            }
146
147
            if (!empty($profile['_id'])) {
148
                $id = $profile['_id'];
149
            } else {
150
                $id = basename($file);
151
            }
152
            if (!empty($profile)) {
153
                $ret[$id] = [
154
                    'profile' => $profile,
155
                    '_id'     => $id,
156
                    'meta'    => $meta,
157
                ];
158
            } else {
159
                $ret[$id] = $profile;
160
            }
161
        }
162
163
        try {
164
            if (!empty($filter->getSort()) && !empty($ret)) {
165
                $this->filter = $filter;
166
                usort($ret, array($this, 'sortByColumn'));
167
                unset($this->filter);
168
            }
169
        } catch (InvalidArgumentException $e) {
170
            // ignore for now.
171
        }
172
173
        $cacheId = md5(serialize($filter->toArray()));
174
175
        $this->countCache[$cacheId] = count($ret);
176
        $ret = array_slice($ret, $filter->getPerPage() * ($filter->getPage() - 1), $filter->getPerPage());
177
        $ret = array_column($ret, null, '_id');
178
179
        return new Xhgui_Storage_ResultSet($ret, $this->countCache[$cacheId]);
180
    }
181
182
    /**
183
     * @inheritDoc
184
     * @param Xhgui_Storage_Filter $filter
185
     * @return int
186
     */
187
    public function count(Xhgui_Storage_Filter $filter)
188
    {
189
        $cacheId = md5(serialize($filter->toArray()));
190
        if (empty($this->countCache[$cacheId])) {
191
            $this->find($filter);
192
        }
193
        return $this->countCache[$cacheId];
194
    }
195
196
    /**
197
     * @inheritDoc
198
     * @param $id
199
     * @return mixed
200
     */
201
    public function findOne($id)
202
    {
203
        $filter = new Xhgui_Storage_Filter();
204
        $filter->setId($id);
205
        $resultSet = $this->find($filter);
206
        return $resultSet->current();
207
    }
208
209
    /**
210
     * @inheritDoc
211
     * @param $id
212
     * @return bool
213
     */
214
    public function remove($id)
215
    {
216
        if (file_exists($this->path . $id)) {
217
            $metaFileName = $this->getMetafileNameFromProfileName($id);
218
            if (file_exists($this->path . $metaFileName)) {
219
                unlink($this->path . $metaFileName);
220
            }
221
            unlink($this->path . $id);
222
            return true;
223
        }
224
        return false;
225
    }
226
227
    /**
228
     * @inheritDoc
229
     */
230
    public function drop()
231
    {
232
        array_map('unlink', glob($this->path . '*.xhprof'));
233
        array_map('unlink', glob($this->path . '*.meta'));
234
    }
235
236
    /**
237
     * @inheritDoc
238
     * @param $match
239
     * @param $col
240
     * @param int $percentile
241
     * @return array
242
     */
243
    public function aggregate(Xhgui_Storage_Filter $filter, $col, $percentile = 1)
244
    {
245
        $ret = $this->find($filter);
246
247
        $result = [
248
            'ok'     => 1,
249
            'result' => [],
250
        ];
251
252
        foreach ($ret as $row) {
253
            $date = \DateTime::createFromFormat(
254
                'U u',
255
                $row['meta']['request_ts_micro']['sec'] . ' ' . $row['meta']['request_ts_micro']['usec']
256
            );
257
            $formattedDate = $date->format('Y-m-d H:i');
258
259 View Code Duplication
            if (empty($result['result'][$formattedDate])) {
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...
260
                $result['result'][$formattedDate] = [
261
                    'wall_times' => [],
262
                    'cpu_times'  => [],
263
                    'mu_times'   => [],
264
                    'pmu_times'  => [],
265
                    'row_count'  => 0
266
                ];
267
            }
268
269
            $result['result'][$formattedDate]['wall_times'][] = $row['profile']['main()']['wt'];
270
            $result['result'][$formattedDate]['cpu_times'][] = $row['profile']['main()']['cpu'];
271
            $result['result'][$formattedDate]['mu_times'][] = $row['profile']['main()']['mu'];
272
            $result['result'][$formattedDate]['pmu_times'][] = $row['profile']['main()']['pmu'];
273
            $result['result'][$formattedDate]['row_count']++;
274
275
            $result['result'][$formattedDate]['raw_index'] =
276
                $result['result'][$formattedDate]['row_count'] * ($percentile / 100);
277
278
            $result['result'][$formattedDate]['_id'] = $date->format('Y-m-d H:i:s');
279
        }
280
281
        return $result;
282
    }
283
284
285
    /**
286
     * Column sorter
287
     *
288
     * @param $a
289
     * @param $b
290
     * @return int
291
     */
292
    public function sortByColumn($a, $b)
293
    {
294
        $sort = $this->filter->getSort();
295
        switch ($sort) {
296
            case 'ct':
297
            case 'wt':
298
            case 'cpu':
299
            case 'mu':
300 View Code Duplication
            case 'pmu':
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...
301
                $aValue = $a['profile']['main()'][$sort];
302
                $bValue = $b['profile']['main()'][$sort];
303
                break;
304
305 View Code Duplication
            case 'time':
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...
306
                $aValue = $a['meta']['request_ts']['sec'];
307
                $bValue = $b['meta']['request_ts']['sec'];
308
                break;
309
310
            case 'controller':
311
            case 'action':
312
            case 'application':
313
            case 'branch':
314 View Code Duplication
            case 'version':
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...
315
                $aValue = $a['meta'][$sort];
316
                $bValue = $b['meta'][$sort];
317
                break;
318
319
            default:
320
                throw new InvalidArgumentException('Invalid sort mode');
321
                break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
322
        }
323
324
        if ($aValue == $bValue) {
325
            return 0;
326
        }
327
328
        if (is_numeric($aValue) || is_numeric($bValue)) {
329
            if ($this->filter->getDirection() === 'desc') {
330
                if ($aValue < $bValue) {
331
                    return 1;
332
                }
333
                return -1;
334
            }
335
336
            if ($aValue > $bValue) {
337
                return 1;
338
            }
339
            return -1;
340
        }
341
342
        if ($this->filter->getDirection() === 'desc') {
343
            return strnatcmp($aValue, $bValue);
344
        }
345
        return strnatcmp($bValue, $aValue);
346
    }
347
348
    /**
349
     * Generate meta profile name from profile file name.
350
     *
351
     * In most cases just add .meta extension
352
     *
353
     * @param $file
354
     * @return mixed
355
     */
356
    protected function getMetafileNameFromProfileName($file)
357
    {
358
        $metaFile = $file . '.meta';
359
        return $metaFile;
360
    }
361
362
    /**
363
     * Load profile file from disk, prepare it and return parsed array
364
     *
365
     * @param $path
366
     * @param bool $meta
367
     * @return mixed
368
     */
369
    protected function importFile($path, $meta = false)
370
    {
371
        if ($meta) {
372
            $serializer = $this->metaSerializer;
373
        } else {
374
            $serializer = $this->dataSerializer;
375
        }
376
377
        if (!file_exists($path) || !is_readable($path)) {
378
            return false;
379
        }
380
381
        switch ($serializer) {
382
            default:
383
            case 'json':
384
                return json_decode(file_get_contents($path), true);
385
386
            case 'serialize':
387
                if (PHP_MAJOR_VERSION > 7) {
388
                    return unserialize(file_get_contents($path), false);
389
                }
390
                /** @noinspection UnserializeExploitsInspection */
391
                return unserialize(file_get_contents($path));
392
393
            case 'igbinary_serialize':
394
            case 'igbinary_unserialize':
395
            case 'igbinary':
396
                /** @noinspection PhpComposerExtensionStubsInspection */
397
                return igbinary_unserialize(file_get_contents($path));
398
399
            // this is a path to a file on disk
400
            case 'php':
401
            case 'var_export':
402
                /** @noinspection PhpIncludeInspection */
403
                return include $path;
404
        }
405
    }
406
407
    /**
408
     * @inheritDoc
409
     * @return array
410
     */
411
    public function getWatchedFunctions()
412
    {
413
        $ret = [];
414
        $files = glob($this->watchedFunctionsPathPrefix . '*.json');
415
        foreach ($files as $file) {
416
            $ret[] = json_decode(file_get_contents($file), true);
417
        }
418
        return $ret;
419
    }
420
421
    /**
422
     * @inheritDoc
423
     * @param $name
424
     * @return bool
425
     */
426 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...
427
    {
428
        $name = trim($name);
429
        if (empty($name)) {
430
            return false;
431
        }
432
        $id = md5($name);
433
        $i = file_put_contents(
434
            $this->watchedFunctionsPathPrefix . $id . '.json',
435
            json_encode(['id' => $id, 'name' => $name])
436
        );
437
        return $i > 0;
438
    }
439
440
    /**
441
     * @inheritDoc
442
     * @param $id
443
     * @param $name
444
     * @return bool
445
     */
446 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...
447
    {
448
        $name = trim($name);
449
        if (empty($name)) {
450
            return false;
451
        }
452
453
        $i = file_put_contents(
454
            $this->watchedFunctionsPathPrefix . $id . '.json',
455
            json_encode(['id' => $id, 'name' => trim($name)])
456
        );
457
        return $i > 0;
458
    }
459
460
    /**
461
     * @inheritDoc
462
     * @param $id
463
     */
464
    public function removeWatchedFunction($id)
465
    {
466
        if (file_exists($this->watchedFunctionsPathPrefix . $id . '.json')) {
467
            unlink($this->watchedFunctionsPathPrefix . $id . '.json');
468
        }
469
    }
470
471
    /**
472
     * Parse filename and try to get request time from filename
473
     *
474
     * @param $fileName
475
     * @return bool|DateTime
476
     */
477
    public function getRequestTimeFromFilename($fileName)
478
    {
479
        $matches = [];
480
        // default pattern is: xhgui.data.<timestamp>.<microseconds>_a68888
481
        //  xhgui.data.15 55 31 04 66 .6606_a68888
482
        preg_match('/(?<t>[\d]{10})(\.(?<m>[\d]{1,6}))?.+/i', $fileName, $matches);
483
        try {
484
            return DateTime::createFromFormat('U u', $matches['t'] . ' ' . $matches['m']);
485
        } catch (Exception $e) {
486
            return null;
487
        }
488
    }
489
}
490