Passed
Pull Request — master (#79)
by Rustam
02:44
created

FileStorage   A

Complexity

Total Complexity 15

Size/Duplication

Total Lines 125
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 4
Bugs 0 Features 0
Metric Value
eloc 56
c 4
b 0
f 0
dl 0
loc 125
ccs 0
cts 58
cp 0
rs 10
wmc 15

8 Methods

Rating   Name   Duplication   Size   Complexity  
A getData() 0 8 2
A __construct() 0 10 1
A setHistorySize() 0 3 1
A read() 0 13 2
A flush() 0 17 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\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 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
    public function __construct(
44
        string $path,
45
        FilesystemInterface $filesystem,
46
        DebuggerIdGenerator $idGenerator,
47
        Aliases $aliases
48
    ) {
49
        $this->path = $path;
50
        $this->filesystem = $filesystem;
51
        $this->idGenerator = $idGenerator;
52
        $this->aliases = $aliases;
53
    }
54
55
    public function addCollector(CollectorInterface $collector): void
56
    {
57
        $this->collectors[get_class($collector)] = $collector;
58
    }
59
60
    public function setHistorySize(int $historySize): void
61
    {
62
        $this->historySize = $historySize;
63
    }
64
65
    public function read($type = self::TYPE_INDEX): array
66
    {
67
        clearstatcache();
68
        $data = [];
69
        $path = $this->aliases->get($this->path);
70
        $dataFiles = glob($path . '/**/**/' . $type . '.json', GLOB_NOSORT);
71
        foreach ($dataFiles as $file) {
72
            $dir = dirname($file);
73
            $id = substr($dir, strlen(dirname($file, 2)) + 1);
74
            $data[$id] = Json::decode(file_get_contents($file));
75
        }
76
77
        return $data;
78
    }
79
80
    public function flush(): void
81
    {
82
        $basePath = $this->path . '/' . date('Y-m-d') . '/' . $this->idGenerator->getId() . '/';
83
        try {
84
            $varDumper = VarDumper::create($this->getData());
85
            $jsonData = $varDumper->asJson();
86
            $this->filesystem->write($basePath . self::TYPE_DATA . '.json', $jsonData);
87
88
            $jsonObjects = $varDumper->asJsonObjectsMap();
89
            $this->filesystem->write($basePath . self::TYPE_OBJECTS . '.json', $jsonObjects);
90
91
            $indexData = VarDumper::create($this->collectIndexData())->asJson();
92
            $this->filesystem->write($basePath . self::TYPE_INDEX . '.json', $indexData);
93
94
            $this->gc();
95
        } finally {
96
            $this->collectors = [];
97
        }
98
    }
99
100
    public function getData(): array
101
    {
102
        $data = [];
103
        foreach ($this->collectors as $collector) {
104
            $data[get_class($collector)] = $collector->getCollected();
105
        }
106
107
        return $data;
108
    }
109
110
    /**
111
     * Collects summary data of current request.
112
     *
113
     * @return array
114
     */
115
    private function collectIndexData(): array
116
    {
117
        $indexData = ['id' => $this->idGenerator->getId()];
118
119
        foreach ($this->collectors as $collector) {
120
            if ($collector instanceof IndexCollectorInterface) {
121
                $indexData = array_merge($indexData, $collector->getIndexData());
122
            }
123
        }
124
125
        return $indexData;
126
    }
127
128
    /**
129
     * Removes obsolete data files
130
     *
131
     * @throws FilesystemException
132
     */
133
    private function gc(): void
134
    {
135
        $indexFiles = glob($this->aliases->get($this->path) . '/**/**/index.json', GLOB_NOSORT);
136
        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

136
        if (count(/** @scrutinizer ignore-type */ $indexFiles) >= $this->historySize + 1) {
Loading history...
137
            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

137
            uasort(/** @scrutinizer ignore-type */ $indexFiles, static fn ($a, $b) => filemtime($b) <=> filemtime($a));
Loading history...
138
            $excessFiles = array_slice($indexFiles, $this->historySize);
139
            foreach ($excessFiles as $file) {
140
                $path1 = dirname($file);
141
                $path2 = dirname($file, 2);
142
                $path3 = dirname($file, 3);
143
                $resource = substr($path1, strlen($path3));
144
                $this->filesystem->deleteDirectory($this->path . $resource);
145
146
                // Clean empty group directories
147
                $group = substr($path2, strlen($path3));
148
                $list = $this->filesystem->listContents($this->path . $group);
149
                if (empty($list->toArray())) {
150
                    $this->filesystem->deleteDirectory($this->path . $group);
151
                }
152
            }
153
        }
154
    }
155
}
156