Passed
Push — master ( a4ffc4...615af5 )
by Alexander
03:23
created

FileStorage::addCollector()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
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\Yii\Debug\Collector\CollectorInterface;
11
use Yiisoft\Yii\Debug\Collector\IndexCollectorInterface;
12
use Yiisoft\Yii\Debug\DebuggerIdGenerator;
13
use Yiisoft\Yii\Debug\Dumper;
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 glob;
22
use function strlen;
23
use function substr;
24
use function uasort;
25
26
final class FileStorage implements StorageInterface
27
{
28
    /**
29
     * @var CollectorInterface[]
30
     */
31
    private array $collectors = [];
32
33
    private string $path;
34
35
    private int $historySize = 50;
36
37
    private DebuggerIdGenerator $idGenerator;
38
39
    private FilesystemInterface $filesystem;
40
41
    private Aliases $aliases;
42
43
    private array $excludedClasses;
44
45 40
    public function __construct(
46
        string $path,
47
        FilesystemInterface $filesystem,
48
        DebuggerIdGenerator $idGenerator,
49
        Aliases $aliases,
50
        array $excludedClasses = []
51
    ) {
52 40
        $this->path = $path;
53 40
        $this->filesystem = $filesystem;
54 40
        $this->idGenerator = $idGenerator;
55 40
        $this->aliases = $aliases;
56 40
        $this->excludedClasses = $excludedClasses;
57
    }
58
59 40
    public function addCollector(CollectorInterface $collector): void
60
    {
61 40
        $this->collectors[$collector->getName()] = $collector;
62
    }
63
64 8
    public function setHistorySize(int $historySize): void
65
    {
66 8
        $this->historySize = $historySize;
67
    }
68
69 16
    public function read($type = self::TYPE_INDEX): array
70
    {
71 16
        clearstatcache();
72 16
        $data = [];
73 16
        $path = $this->aliases->get($this->path);
74 16
        $dataFiles = glob($path . '/**/**/' . $type . '.json', GLOB_NOSORT);
75 16
        uasort($dataFiles, static fn ($a, $b) => filemtime($a) <=> filemtime($b));
76
77 16
        foreach ($dataFiles as $file) {
78
            $dir = dirname($file);
79
            $id = substr($dir, strlen(dirname($file, 2)) + 1);
80
            $data[$id] = Json::decode(file_get_contents($file));
81
        }
82
83 16
        return $data;
84
    }
85
86 32
    public function flush(): void
87
    {
88 32
        $basePath = $this->path . '/' . date('Y-m-d') . '/' . $this->idGenerator->getId() . '/';
89
        try {
90 32
            $dumper = Dumper::create($this->getData(), $this->excludedClasses);
91 32
            $jsonData = $dumper->asJson();
92 32
            $this->filesystem->write($basePath . self::TYPE_DATA . '.json', $jsonData);
93
94 32
            $jsonObjects = json_decode($dumper->asJsonObjectsMap(), true);
95 32
            $jsonObjects = $this->reindexObjects($jsonObjects);
96 32
            $this->filesystem->write($basePath . self::TYPE_OBJECTS . '.json', Dumper::create($jsonObjects)->asJson());
97
98 32
            $indexData = Dumper::create($this->collectIndexData())->asJson();
99 32
            $this->filesystem->write($basePath . self::TYPE_INDEX . '.json', $indexData);
100
101 32
            $this->gc();
102 32
        } finally {
103 32
            $this->collectors = [];
104
        }
105
    }
106
107 40
    public function getData(): array
108
    {
109 40
        $data = [];
110 40
        foreach ($this->collectors as $name => $collector) {
111 40
            $data[$name] = $collector->getCollected();
112
        }
113
114 40
        return $data;
115
    }
116
117 8
    public function clear(): void
118
    {
119 8
        $this->filesystem->deleteDirectory($this->path);
120
    }
121
122
    /**
123
     * Collects summary data of current request.
124
     *
125
     * @return array
126
     */
127 32
    private function collectIndexData(): array
128
    {
129 32
        $indexData = ['id' => $this->idGenerator->getId()];
130
131 32
        foreach ($this->collectors as $collector) {
132 32
            if ($collector instanceof IndexCollectorInterface) {
133
                $indexData = array_merge($indexData, $collector->getIndexData());
134
            }
135
        }
136
137 32
        return $indexData;
138
    }
139
140
    /**
141
     * Removes obsolete data files
142
     *
143
     * @throws FilesystemException
144
     */
145 32
    private function gc(): void
146
    {
147 32
        $indexFiles = glob($this->aliases->get($this->path) . '/**/**/index.json', GLOB_NOSORT);
148 32
        if (count($indexFiles) >= $this->historySize + 1) {
149
            uasort($indexFiles, static fn ($a, $b) => filemtime($b) <=> filemtime($a));
150
            $excessFiles = array_slice($indexFiles, $this->historySize);
151
            foreach ($excessFiles as $file) {
152
                $path1 = dirname($file);
153
                $path2 = dirname($file, 2);
154
                $path3 = dirname($file, 3);
155
                $resource = substr($path1, strlen($path3));
156
                $this->filesystem->deleteDirectory($this->path . $resource);
157
158
                // Clean empty group directories
159
                $group = substr($path2, strlen($path3));
160
                $list = $this->filesystem->listContents($this->path . $group);
161
                if (empty($list->toArray())) {
162
                    $this->filesystem->deleteDirectory($this->path . $group);
163
                }
164
            }
165
        }
166
    }
167
168 32
    private function reindexObjects(array $objectsAsArraysCollection): array
169
    {
170 32
        $result = [];
171 32
        foreach ($objectsAsArraysCollection as $objectAsArray) {
172 4
            $result = array_merge($result, $objectAsArray);
173
        }
174
175 32
        return $result;
176
    }
177
}
178