Passed
Pull Request — master (#178)
by Dmitriy
09:43 queued 05:24
created

FileStorage::hasActiveCollector()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

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