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

Xhgui_Storage_File::sortByColumn()   D

Complexity

Conditions 19
Paths 78

Size

Total Lines 55

Duplication

Lines 12
Ratio 21.82 %

Importance

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