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

Xhgui_Storage_File::find()   D

Complexity

Conditions 17
Paths 160

Size

Total Lines 80

Duplication

Lines 0
Ratio 0 %

Importance

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