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

Xhgui_Storage_File::find()   F

Complexity

Conditions 28
Paths 704

Size

Total Lines 104

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 104
rs 0.3288
c 0
b 0
f 0
cc 28
nc 704
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
            if (!empty($profile['profile'])) {
135
                $profile = $profile['profile'];
136
            }
137
138
            if (!empty($profile['_id'])) {
139
                $id = $profile['_id'];
140
            } else {
141
                $id = basename($file);
142
            }
143
            if (!empty($profile)) {
144
                $ret[$id] = [
145
                    'profile'   => $profile,
146
                    '_id'       => $id,
147
                    'meta'      => $meta,
148
                ];
149
            } else {
150
                $ret[$id] = $profile;
151
            }
152
        }
153
154
        try {
155
            if (!empty($filter->getSort()) && !empty($ret)) {
156
                $this->filter = $filter;
157
                usort($ret, array($this, 'sortByColumn'));
158
                unset($this->filter);
159
            }
160
        } catch (InvalidArgumentException $e) {
161
            // ignore for now.
162
        }
163
        
164
        $cacheId = md5(serialize($filter->toArray()));
165
166
        $this->countCache[$cacheId] = count($ret);
167
        $ret = array_slice($ret, $filter->getPerPage()*($filter->getPage()-1), $filter->getPerPage());
168
        $ret = array_column($ret, null, '_id');
169
170
        return new Xhgui_Storage_ResultSet($ret, $this->countCache[$cacheId]);
171
    }
172
173
    /**
174
     * @inheritDoc
175
     * @param Xhgui_Storage_Filter $filter
176
     * @return int
177
     */
178
    public function count(Xhgui_Storage_Filter $filter)
179
    {
180
        $cacheId = md5(serialize($filter->toArray()));
181
        if (empty($this->countCache[$cacheId])) {
182
            $this->find($filter);
183
        }
184
        return $this->countCache[$cacheId];
185
    }
186
187
    /**
188
     * @inheritDoc
189
     * @param $id
190
     * @return mixed
191
     */
192
    public function findOne($id)
193
    {
194
        $filter = new Xhgui_Storage_Filter();
195
        $filter->setId($id);
196
        $resultSet = $this->find($filter);
197
        return $resultSet->current();
198
    }
199
200
    /**
201
     * @inheritDoc
202
     * @param $id
203
     * @return bool
204
     */
205
    public function remove($id)
206
    {
207
        if (file_exists($this->path.$id)) {
208
            $metaFileName = $this->getMetafileNameFromProfileName($id);
209
            if (file_exists($this->path.$metaFileName)) {
210
                unlink($this->path.$metaFileName);
211
            }
212
            unlink($this->path.$id);
213
            return true;
214
        }
215
        return false;
216
    }
217
218
    /**
219
     * @inheritDoc
220
     */
221
    public function drop()
222
    {
223
        array_map('unlink', glob($this->path.'*.xhprof'));
224
        array_map('unlink', glob($this->path.'*.meta'));
225
    }
226
227
    /**
228
     * @inheritDoc
229
     * @param $match
230
     * @param $col
231
     * @param int $percentile
232
     * @return array
233
     */
234
    public function aggregate(Xhgui_Storage_Filter $filter, $col, $percentile = 1)
235
    {
236
        $ret = $this->find($filter);
237
238
        $result = [
239
            'ok'        => 1,
240
            'result'    => [],
241
        ];
242
243
        foreach ($ret as $row) {
244
            $date = \DateTime::createFromFormat(
245
                'U u',
246
                $row['meta']['request_ts_micro']['sec'].' '.$row['meta']['request_ts_micro']['usec']
247
            );
248
            $formattedDate = $date->format('Y-m-d H:i');
249
250 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...
251
                $result['result'][$formattedDate] = [
252
                    'wall_times'    => [],
253
                    'cpu_times'     => [],
254
                    'mu_times'      => [],
255
                    'pmu_times'     => [],
256
                    'row_count'     => 0
257
                ];
258
            }
259
260
            $result['result'][$formattedDate]['wall_times'][]    = $row['profile']['main()']['wt'];
261
            $result['result'][$formattedDate]['cpu_times'][]     = $row['profile']['main()']['cpu'];
262
            $result['result'][$formattedDate]['mu_times'][]      = $row['profile']['main()']['mu'];
263
            $result['result'][$formattedDate]['pmu_times'][]     = $row['profile']['main()']['pmu'];
264
            $result['result'][$formattedDate]['row_count']++;
265
266
            $result['result'][$formattedDate]['raw_index'] =
267
                $result['result'][$formattedDate]['row_count']*($percentile/100);
268
269
            $result['result'][$formattedDate]['_id']= $date->format('Y-m-d H:i:s');
270
        }
271
272
        return $result;
273
    }
274
275
276
    /**
277
     * Column sorter
278
     *
279
     * @param $a
280
     * @param $b
281
     * @return int
282
     */
283
    public function sortByColumn($a, $b)
284
    {
285
        $sort = $this->filter->getSort();
286
        switch ($sort) {
287
            case 'ct':
288
            case 'wt':
289
            case 'cpu':
290
            case 'mu':
291 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...
292
                $aValue = $a['profile']['main()'][$sort];
293
                $bValue = $b['profile']['main()'][$sort];
294
                break;
295
296 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...
297
                $aValue = $a['meta']['request_ts']['sec'];
298
                $bValue = $b['meta']['request_ts']['sec'];
299
                break;
300
301
            case 'controller':
302
            case 'action':
303
            case 'application':
304
            case 'branch':
305 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...
306
                $aValue = $a['meta'][$sort];
307
                $bValue = $b['meta'][$sort];
308
                break;
309
310
            default:
311
                throw new InvalidArgumentException('Invalid sort mode');
312
                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...
313
        }
314
315
        if ($aValue == $bValue) {
316
            return 0;
317
        }
318
319
        if (is_numeric($aValue) || is_numeric($bValue)) {
320
            if ($this->filter->getDirection() === 'desc') {
321
                if ($aValue < $bValue) {
322
                    return 1;
323
                }
324
                return -1;
325
            }
326
327
            if ($aValue > $bValue) {
328
                return 1;
329
            }
330
            return -1;
331
        }
332
333
        if ($this->filter->getDirection() === 'desc') {
334
            return strnatcmp($aValue, $bValue);
335
        }
336
        return strnatcmp($bValue, $aValue);
337
    }
338
339
    /**
340
     * Generate meta profile name from profile file name.
341
     *
342
     * In most cases just add .meta extension
343
     *
344
     * @param $file
345
     * @return mixed
346
     */
347
    protected function getMetafileNameFromProfileName($file)
348
    {
349
        $metaFile = $file.'.meta';
350
        return $metaFile;
351
    }
352
353
    /**
354
     * Load profile file from disk, prepare it and return parsed array
355
     *
356
     * @param $path
357
     * @param bool $meta
358
     * @return mixed
359
     */
360
    protected function importFile($path, $meta = false)
361
    {
362
        if ($meta) {
363
            $serializer = $this->metaSerializer;
364
        } else {
365
            $serializer = $this->dataSerializer;
366
        }
367
368
        if (!file_exists($path) || !is_readable($path)) {
369
            return false;
370
        }
371
        
372
        switch ($serializer) {
373
            default:
374
            case 'json':
375
                return json_decode(file_get_contents($path), true);
376
377
            case 'serialize':
378
                if (PHP_MAJOR_VERSION > 7) {
379
                    return unserialize(file_get_contents($path), false);
380
                }
381
                /** @noinspection UnserializeExploitsInspection */
382
                return unserialize(file_get_contents($path));
383
384
            case 'igbinary_serialize':
385
            case 'igbinary_unserialize':
386
            case 'igbinary':
387
                /** @noinspection PhpComposerExtensionStubsInspection */
388
                return igbinary_unserialize(file_get_contents($path));
389
390
            // this is a path to a file on disk
391
            case 'php':
392
            case 'var_export':
393
                /** @noinspection PhpIncludeInspection */
394
                return include $path;
395
        }
396
    }
397
398
    /**
399
     * @inheritDoc
400
     * @return array
401
     */
402
    public function getWatchedFunctions()
403
    {
404
        $ret = [];
405
        $files = glob($this->watchedFunctionsPathPrefix.'*.json');
406
        foreach ($files as $file) {
407
            $ret[] = json_decode(file_get_contents($file), true);
408
        }
409
        return $ret;
410
    }
411
412
    /**
413
     * @inheritDoc
414
     * @param $name
415
     * @return bool
416
     */
417 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...
418
    {
419
        $name = trim($name);
420
        if (empty($name)) {
421
            return false;
422
        }
423
        $id = md5($name);
424
        $i = file_put_contents(
425
            $this->watchedFunctionsPathPrefix.$id.'.json',
426
            json_encode(['id'=>$id, 'name'=>$name])
427
        );
428
        return $i > 0;
429
    }
430
431
    /**
432
     * @inheritDoc
433
     * @param $id
434
     * @param $name
435
     * @return bool
436
     */
437 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...
438
    {
439
        $name = trim($name);
440
        if (empty($name)) {
441
            return false;
442
        }
443
444
        $i = file_put_contents(
445
            $this->watchedFunctionsPathPrefix.$id.'.json',
446
            json_encode(['id'=>$id, 'name'=>trim($name)])
447
        );
448
        return $i > 0;
449
    }
450
451
    /**
452
     * @inheritDoc
453
     * @param $id
454
     */
455
    public function removeWatchedFunction($id)
456
    {
457
        if (file_exists($this->watchedFunctionsPathPrefix.$id.'.json')) {
458
            unlink($this->watchedFunctionsPathPrefix.$id.'.json');
459
        }
460
    }
461
462
    /**
463
     * Parse filename and try to get request time from filename
464
     *
465
     * @param $fileName
466
     * @return bool|DateTime
467
     */
468
    public function getRequestTimeFromFilename($fileName)
469
    {
470
        $matches = [];
471
        // default pattern is: xhgui.data.<timestamp>.<microseconds>_a68888
472
        //  xhgui.data.15 55 31 04 66 .6606_a68888
473
        preg_match('/(?<t>[\d]{10})(\.(?<m>[\d]{1,6}))?.+/i', $fileName, $matches);
474
        try {
475
            return DateTime::createFromFormat('U u', $matches['t'].' '. $matches['m']);
476
        } catch (Exception $e) {
477
            return null;
478
        }
479
    }
480
}
481