Completed
Pull Request — master (#268)
by
unknown
02:52 queued 01:29
created

Xhgui_Storage_File   F

Complexity

Total Complexity 73

Size/Duplication

Total Lines 468
Duplicated Lines 6.84 %

Coupling/Cohesion

Components 2
Dependencies 3

Importance

Changes 0
Metric Value
wmc 73
lcom 2
cbo 3
dl 32
loc 468
rs 2.56
c 0
b 0
f 0

17 Methods

Rating   Name   Duplication   Size   Complexity  
A getWatchedFunctions() 0 9 2
A __construct() 0 9 1
D find() 0 80 17
A count() 0 8 2
A findOne() 0 7 1
A remove() 0 12 3
A drop() 0 5 1
A aggregate() 0 29 3
D sortByColumn() 12 55 19
A getMetafileNameFromProfileName() 0 5 1
A insert() 0 9 2
A update() 0 4 1
C importFile() 0 37 12
A addWatchedFunction() 10 13 2
A updateWatchedFunction() 10 13 2
A removeWatchedFunction() 0 6 2
A getRequestTimeFromFilename() 0 12 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Xhgui_Storage_File often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Xhgui_Storage_File, and based on these observations, apply Extract Interface, too.

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