Completed
Pull Request — master (#268)
by
unknown
01:26
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 Xhgui_StorageInterface, Xhgui_WatchedFunctionsStorageInterface
4
{
5
6
    /**
7
     * @var string
8
     */
9
    protected $path     = '../data/';
10
11
    /**
12
     * @var string
13
     */
14
    protected $prefix   = 'xhgui.data';
15
16
    /**
17
     * @var bool|mixed
18
     */
19
    protected $separateMeta = true;
20
21
    /**
22
     * @var mixed
23
     */
24
    protected $dataSerializer;
25
26
    /**
27
     * @var mixed
28
     */
29
    protected $metaSerializer;
30
31
    /**
32
     * @var string
33
     */
34
    protected $watchedFunctionsPathPrefix = '../watched_functions/';
35
36
    /**
37
     * @var int[]
38
     */
39
    protected $countCache;
40
41
    /**
42
     * @var Xhgui_Storage_Filter
43
     */
44
    private $filter;
45
46
    /**
47
     * Xhgui_Storage_File constructor.
48
     * @param $config
49
     */
50
    public function __construct($config)
51
    {
52
53
        // @todo config!
54
        $this->path         = '../data/';
55
56
        // @todo config!
57
        $this->prefix       = 'xhgui.data';
58
59
        $this->separateMeta     = $config['save.handler.separate_meta'];
60
        $this->dataSerializer   = $config['save.handler.serializer'];
61
        $this->metaSerializer   = $config['save.handler.meta_serializer'];
62
    }
63
64
    /**
65
     * @inheritDoc
66
     * @param Xhgui_Storage_Filter $filter
67
     * @param bool $projections
68
     * @return Xhgui_Storage_ResultSet
69
     */
70
    public function find(Xhgui_Storage_Filter $filter, $projections = false)
71
    {
72
        $result       = glob($this->path. $this->prefix . '*');
73
        sort($result);
74
75
        $ret = [];
76
        foreach ($result as $i => $file) {
77
            // skip meta files.
78
            if (strpos($file, '.meta') !== false) {
79
                continue;
80
            }
81
82
            // try to detect timestamp in filename.
83
            $requestTimeFromFilename = $this->getRequestTimeFromFilename($file);
84
            if (!empty($requestTimeFromFilename)) {
85
                if (null !== $filter->getStartDate() &&
86
                    $this->getDateTimeFromString($filter->getStartDate(), 'start') >= $requestTimeFromFilename) {
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...
87
                    continue;
88
                }
89
90
                if (null !== $filter->getEndDate() &&
91
                    $this->getDateTimeFromString($filter->getEndDate(), 'end') <= $requestTimeFromFilename ) {
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...
92
                    continue;
93
                }
94
            }
95
96
            $metaFile   = $this->getMetafileNameFromProfileName($file);
97
98
            $meta       = $this->importFile($metaFile, true);
99
            if ($meta === false) {
100
                continue;
101
            }
102
103
            $profile    = $this->importFile($file, false);
104
            if ($profile === false) {
105
                continue;
106
            }
107
108
            if (!empty($profile['meta'])) {
109
                $meta = array_merge($meta, $profile['meta']);
110
            }
111
112
            if (!empty($profile['profile'])) {
113
                $profile = $profile['profile'];
114
            }
115
116
            if (!empty($profile['_id'])) {
117
                $id = $profile['_id'];
118
            } else {
119
                $id = basename($file);
120
            }
121
            if (!empty($profile)) {
122
                $ret[$id] = [
123
                    'profile'   => $profile,
124
                    '_id'       => $id,
125
                    'meta'      => $meta,
126
                ];
127
            } else {
128
                $ret[$id] = $profile;
129
            }
130
        }
131
132
        try {
133
            if (!empty($filter->getSort()) && !empty($ret)) {
134
                $this->filter = $filter;
135
                usort($ret, array($this, 'sortByColumn'));
136
                unset($this->filter);
137
            }
138
        } catch (InvalidArgumentException $e) {
139
            // ignore for now.
140
        }
141
        
142
        $cacheId = md5(serialize($filter->toArray()));
143
144
        $this->countCache[$cacheId] = count($ret);
145
        $ret = array_slice($ret, $filter->getPerPage()*($filter->getPage()-1), $filter->getPerPage());
146
        $ret = array_column($ret, null, '_id');
147
148
        return new Xhgui_Storage_ResultSet($ret, $this->countCache[$cacheId]);
149
    }
150
151
    /**
152
     * @inheritDoc
153
     * @param Xhgui_Storage_Filter $filter
154
     * @return int
155
     */
156
    public function count(Xhgui_Storage_Filter $filter)
157
    {
158
        $cacheId = md5(serialize($filter->toArray()));
159
        if (empty($this->countCache[$cacheId])) {
160
            $this->find($filter);
161
        }
162
        return $this->countCache[$cacheId];
163
    }
164
165
    /**
166
     * @inheritDoc
167
     * @param $id
168
     * @return mixed
169
     */
170
    public function findOne($id)
171
    {
172
        $filter = new Xhgui_Storage_Filter();
173
        $filter->setId($id);
174
        $resultSet = $this->find($id);
175
        return $resultSet->current();
176
    }
177
178
    /**
179
     * @inheritDoc
180
     * @param $id
181
     * @return bool
182
     */
183
    public function remove($id)
184
    {
185
        if (file_exists($this->path.$id)) {
186
            $metaFileName = $this->getMetafileNameFromProfileName($id);
187
            if (file_exists($this->path.$metaFileName)) {
188
                unlink($this->path.$metaFileName);
189
            }
190
            unlink($this->path.$id);
191
            return true;
192
        }
193
        return false;
194
    }
195
196
    /**
197
     * @inheritDoc
198
     */
199
    public function drop()
200
    {
201
        array_map('unlink', glob($this->path.'*.xhprof'));
202
        array_map('unlink', glob($this->path.'*.meta'));
203
    }
204
205
    /**
206
     * @inheritDoc
207
     * @param $match
208
     * @param $col
209
     * @param int $percentile
210
     * @return array
211
     */
212
    public function aggregate(Xhgui_Storage_Filter $filter, $col, $percentile = 1)
213
    {
214
        $ret = $this->find($filter);
215
216
        $result = [
217
            'ok'        => 1,
218
            'result'    => [],
219
        ];
220
221
        foreach ($ret as $row) {
222
            $request_date = $row['meta']['request_date'];
223
            $result['result'][$request_date]['wall_times'][]    = $row['profile']['main()']['wt'];
224
            $result['result'][$request_date]['cpu_times'][]     = $row['profile']['main()']['cpu'];
225
            $result['result'][$request_date]['mu_times'][]      = $row['profile']['main()']['mu'];
226
            $result['result'][$request_date]['pmu_times'][]     = $row['profile']['main()']['pmu'];
227
228
            if (empty($result['result'][$request_date]['row_count'])) {
229
                $result['result'][$request_date]['row_count'] = 0;
230
            }
231
            $result['result'][$request_date]['row_count']++;
232
233
            $result['result'][$request_date]['raw_index'] =
234
                $result['result'][$request_date]['row_count']*($percentile/100);
235
236
            $result['result'][$request_date]['_id']= $request_date;
237
        }
238
239
        return $result;
240
    }
241
242
243
    /**
244
     * Column sorter
245
     *
246
     * @param $a
247
     * @param $b
248
     * @return int
249
     */
250
    public function sortByColumn($a, $b)
251
    {
252
        $sort = $this->filter->getSort();
253
        switch ($sort) {
254
            case 'ct':
255
            case 'wt':
256
            case 'cpu':
257
            case 'mu':
258 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...
259
                $aValue = $a['profile']['main()'][$sort];
260
                $bValue = $b['profile']['main()'][$sort];
261
                break;
262
263 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...
264
                $aValue = $a['meta']['request_ts']['sec'];
265
                $bValue = $b['meta']['request_ts']['sec'];
266
                break;
267
268
            case 'controller':
269
            case 'action':
270
            case 'application':
271
            case 'branch':
272 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...
273
                $aValue = $a['meta'][$sort];
274
                $bValue = $b['meta'][$sort];
275
                break;
276
277
            default:
278
                throw new InvalidArgumentException('Invalid sort mode');
279
                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...
280
        }
281
282
        if ($aValue == $bValue) {
283
            return 0;
284
        }
285
286
        if (is_numeric($aValue) || is_numeric($bValue)) {
287
            if ($this->filter->getDirection() === 'desc') {
288
                if ($aValue < $bValue) {
289
                    return 1;
290
                }
291
                return -1;
292
            }
293
294
            if ($aValue > $bValue) {
295
                return 1;
296
            }
297
            return -1;
298
        }
299
300
        if ($this->filter->getDirection() === 'desc') {
301
            return strnatcmp($aValue, $bValue);
302
        }
303
        return strnatcmp($bValue, $aValue);
304
    }
305
306
    /**
307
     * Generate meta profile name from profile file name.
308
     *
309
     * In most cases just add .meta extension
310
     *
311
     * @param $file
312
     * @return mixed
313
     */
314
    protected function getMetafileNameFromProfileName($file)
315
    {
316
        $metaFile = $file.'.meta';
317
        return $metaFile;
318
    }
319
320
321
    /**
322
     * @inheritDoc
323
     * @param $data
324
     */
325
    public function insert(array $data)
326
    {
327
328
        if (empty($data['_id'])) {
329
            $data['_id'] = md5($data['name']);
330
        }
331
332
        file_put_contents($this->path.''.$this->prefix.$data['_id'].'.json', json_encode($data));
333
    }
334
335
    /**
336
     * @inheritDoc
337
     * @param $id
338
     * @param $data
339
     */
340
    public function update($id, array $data)
341
    {
342
        file_put_contents($this->path.''.$this->prefix.$id.'.json', json_encode($data));
343
    }
344
345
    /**
346
     * Load profile file from disk, prepare it and return parsed array
347
     *
348
     * @param $path
349
     * @param bool $meta
350
     * @return mixed
351
     */
352
    protected function importFile($path, $meta = false)
353
    {
354
        if ($meta) {
355
            $serializer = $this->metaSerializer;
356
        } else {
357
            $serializer = $this->dataSerializer;
358
        }
359
360
        if (!file_exists($path) || !is_readable($path)) {
361
            return false;
362
        }
363
        
364
        switch ($serializer) {
365
            default:
366
            case 'json':
367
                return json_decode(file_get_contents($path), true);
368
369
            case 'serialize':
370
                if (PHP_MAJOR_VERSION > 7) {
371
                    return unserialize(file_get_contents($path), false);
372
                }
373
                /** @noinspection UnserializeExploitsInspection */
374
                return unserialize(file_get_contents($path));
375
376
            case 'igbinary_serialize':
377
            case 'igbinary_unserialize':
378
            case 'igbinary':
379
                /** @noinspection PhpComposerExtensionStubsInspection */
380
                return igbinary_unserialize(file_get_contents($path));
381
382
            // this is a path to a file on disk
383
            case 'php':
384
            case 'var_export':
385
                /** @noinspection PhpIncludeInspection */
386
                return include $path;
387
        }
388
    }
389
390
    /**
391
     * @inheritDoc
392
     * @return array
393
     */
394
    public function getWatchedFunctions()
395
    {
396
        $ret = [];
397
        $files = glob($this->watchedFunctionsPathPrefix.'*.json');
398
        foreach ($files as $file) {
399
            $ret[] = json_decode(file_get_contents($file));
400
        }
401
        return $ret;
402
    }
403
404
    /**
405
     * @inheritDoc
406
     * @param $name
407
     * @return bool
408
     */
409 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...
410
    {
411
        $name = trim($name);
412
        if (empty($name)) {
413
            return false;
414
        }
415
        $id = md5($name);
416
        $i = file_put_contents(
417
            $this->watchedFunctionsPathPrefix.$id.'.json',
418
            json_encode(['id'=>$id, 'name'=>$name])
419
        );
420
        return $i > 0;
421
    }
422
423
    /**
424
     * @inheritDoc
425
     * @param $id
426
     * @param $name
427
     * @return bool
428
     */
429 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...
430
    {
431
        $name = trim($name);
432
        if (empty($name)) {
433
            return false;
434
        }
435
436
        $i = file_put_contents(
437
            $this->watchedFunctionsPathPrefix.$id.'.json',
438
            json_encode(['id'=>$id, 'name'=>trim($name)])
439
        );
440
        return $i > 0;
441
    }
442
443
    /**
444
     * @inheritDoc
445
     * @param $id
446
     */
447
    public function removeWatchedFunction($id)
448
    {
449
        if (file_exists($this->watchedFunctionsPathPrefix.$id.'.json')) {
450
            unlink($this->watchedFunctionsPathPrefix.$id.'.json');
451
        }
452
    }
453
454
    /**
455
     * Parse filename and try to get request time from filename
456
     *
457
     * @param $fileName
458
     * @return bool|DateTime
459
     */
460
    public function getRequestTimeFromFilename($fileName)
461
    {
462
        $matches = [];
463
        // default pattern is: xhgui.data.<timestamp>.<microseconds>_a68888
464
        //  xhgui.data.15 55 31 04 66 .6606_a68888
465
        preg_match('/(?<t>[\d]{10})(\.(?<m>[\d]{1,6}))?.+/i', $fileName, $matches);
466
        try {
467
            return DateTime::createFromFormat('U u', $matches['t'].' '. $matches['m']);
468
        } catch (Exception $e) {
469
            return null;
470
        }
471
    }
472
}
473