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