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

Xhgui_Storage_File::importFile()   C

Complexity

Conditions 12
Paths 20

Size

Total Lines 37

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 37
rs 6.9666
c 0
b 0
f 0
cc 12
nc 20
nop 2

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