Passed
Pull Request — master (#96)
by Dmitriy
03:14
created

FileStorage   A

Complexity

Total Complexity 18

Size/Duplication

Total Lines 150
Duplicated Lines 0 %

Test Coverage

Coverage 78.08%

Importance

Changes 7
Bugs 0 Features 0
Metric Value
eloc 65
c 7
b 0
f 0
dl 0
loc 150
ccs 57
cts 73
cp 0.7808
rs 10
wmc 18

10 Methods

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