Passed
Push — master ( 373aa8...4ff789 )
by Alexander
35:52 queued 33:18
created

FileStorage::setHistorySize()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
ccs 0
cts 2
cp 0
crap 2
rs 10
c 1
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Yii\Debug\Storage;
6
7
use League\Flysystem\FilesystemException;
8
use Yiisoft\Aliases\Aliases;
9
use Yiisoft\Json\Json;
10
use Yiisoft\VarDumper\VarDumper;
11
use Yiisoft\Yii\Debug\Collector\CollectorInterface;
12
use Yiisoft\Yii\Debug\Collector\IndexCollectorInterface;
13
use Yiisoft\Yii\Debug\DebuggerIdGenerator;
14
use Yiisoft\Yii\Filesystem\FilesystemInterface;
15
16
use function array_merge;
17
use function array_slice;
18
use function count;
19
use function dirname;
20
use function filemtime;
21
use function get_class;
22
use function glob;
23
use function strlen;
24
use function substr;
25
use function uasort;
26
27
final class FileStorage implements StorageInterface
28
{
29
    /**
30
     * @var CollectorInterface[]
31
     */
32
    private array $collectors = [];
33
34
    private string $path;
35
36
    private int $historySize = 50;
37
38
    private DebuggerIdGenerator $idGenerator;
39
40
    private FilesystemInterface $filesystem;
41
42
    private Aliases $aliases;
43
44
    public function __construct(
45
        string $path,
46
        FilesystemInterface $filesystem,
47
        DebuggerIdGenerator $idGenerator,
48
        Aliases $aliases
49
    ) {
50
        $this->path = $path;
51
        $this->filesystem = $filesystem;
52
        $this->idGenerator = $idGenerator;
53
        $this->aliases = $aliases;
54
    }
55
56
    public function addCollector(CollectorInterface $collector): void
57
    {
58
        $this->collectors[get_class($collector)] = $collector;
59
    }
60
61
    public function setHistorySize(int $historySize): void
62
    {
63
        $this->historySize = $historySize;
64
    }
65
66
    public function read($type = self::TYPE_INDEX): array
67
    {
68
        clearstatcache();
69
        $data = [];
70
        $path = $this->aliases->get($this->path);
71
        $dataFiles = glob($path . '/**/**/' . $type . '.json', GLOB_NOSORT);
72
        foreach ($dataFiles as $file) {
73
            $dir = dirname($file);
74
            $id = substr($dir, strlen(dirname($file, 2)) + 1);
75
            $data[$id] = Json::decode(file_get_contents($file));
76
        }
77
78
        return $data;
79
    }
80
81
    public function flush(): void
82
    {
83
        $basePath = $this->path . '/' . date('Y-m-d') . '/' . $this->idGenerator->getId() . '/';
84
        try {
85
            $varDumper = VarDumper::create($this->getData());
86
            $jsonData = $varDumper->asJson();
87
            $this->filesystem->write($basePath . self::TYPE_DATA . '.json', $jsonData);
88
89
            $jsonObjects = json_decode($varDumper->asJsonObjectsMap(), true);
90
            $jsonObjects = $this->reindexObjects($jsonObjects);
91
            $this->filesystem->write($basePath . self::TYPE_OBJECTS . '.json', VarDumper::create($jsonObjects)->asJson());
92
93
            $indexData = VarDumper::create($this->collectIndexData())->asJson();
94
            $this->filesystem->write($basePath . self::TYPE_INDEX . '.json', $indexData);
95
96
            $this->gc();
97
        } finally {
98
            $this->collectors = [];
99
        }
100
    }
101
102
    public function getData(): array
103
    {
104
        $data = [];
105
        foreach ($this->collectors as $collector) {
106
            $data[get_class($collector)] = $collector->getCollected();
107
        }
108
109
        return $data;
110
    }
111
112
    /**
113
     * Collects summary data of current request.
114
     *
115
     * @return array
116
     */
117
    private function collectIndexData(): array
118
    {
119
        $indexData = ['id' => $this->idGenerator->getId()];
120
121
        foreach ($this->collectors as $collector) {
122
            if ($collector instanceof IndexCollectorInterface) {
123
                $indexData = array_merge($indexData, $collector->getIndexData());
124
            }
125
        }
126
127
        return $indexData;
128
    }
129
130
    /**
131
     * Removes obsolete data files
132
     *
133
     * @throws FilesystemException
134
     */
135
    private function gc(): void
136
    {
137
        $indexFiles = glob($this->aliases->get($this->path) . '/**/**/index.json', GLOB_NOSORT);
138
        if (count($indexFiles) >= $this->historySize + 1) {
0 ignored issues
show
Bug introduced by
It seems like $indexFiles can also be of type false; however, parameter $var of count() does only seem to accept Countable|array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

138
        if (count(/** @scrutinizer ignore-type */ $indexFiles) >= $this->historySize + 1) {
Loading history...
139
            uasort($indexFiles, static fn ($a, $b) => filemtime($b) <=> filemtime($a));
0 ignored issues
show
Bug introduced by
It seems like $indexFiles can also be of type false; however, parameter $array of uasort() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

139
            uasort(/** @scrutinizer ignore-type */ $indexFiles, static fn ($a, $b) => filemtime($b) <=> filemtime($a));
Loading history...
140
            $excessFiles = array_slice($indexFiles, $this->historySize);
141
            foreach ($excessFiles as $file) {
142
                $path1 = dirname($file);
143
                $path2 = dirname($file, 2);
144
                $path3 = dirname($file, 3);
145
                $resource = substr($path1, strlen($path3));
146
                $this->filesystem->deleteDirectory($this->path . $resource);
147
148
                // Clean empty group directories
149
                $group = substr($path2, strlen($path3));
150
                $list = $this->filesystem->listContents($this->path . $group);
151
                if (empty($list->toArray())) {
152
                    $this->filesystem->deleteDirectory($this->path . $group);
153
                }
154
            }
155
        }
156
    }
157
158
    private function reindexObjects(array $objectsAsArraysCollection): array
159
    {
160
        $result = [];
161
        foreach ($objectsAsArraysCollection as $objectAsArray) {
162
            $result = array_merge($result, $objectAsArray);
163
        }
164
165
        return $result;
166
    }
167
}
168