Passed
Pull Request — master (#257)
by Alexander
04:24
created

FileStorage::reindexObjects()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2.7462

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 4
nc 2
nop 1
dl 0
loc 8
ccs 3
cts 7
cp 0.4286
crap 2.7462
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 Yiisoft\Files\FileHelper;
8
use Yiisoft\Json\Json;
9
use Yiisoft\Yii\Debug\Collector\CollectorInterface;
10
use Yiisoft\Yii\Debug\Collector\SummaryCollectorInterface;
11
use Yiisoft\Yii\Debug\DebuggerIdGenerator;
12
use Yiisoft\Yii\Debug\Dumper;
13
14
use function array_merge;
15
use function array_slice;
16
use function count;
17
use function dirname;
18
use function filemtime;
19
use function glob;
20
use function strlen;
21
use function substr;
22
use function uasort;
23
24
final class FileStorage implements StorageInterface
25
{
26
    /**
27
     * @var CollectorInterface[]
28
     */
29
    private array $collectors = [];
30
31
    private int $historySize = 50;
32
33
    public function __construct(
34
        private readonly string $path,
35
        private readonly DebuggerIdGenerator $idGenerator,
36
        private readonly array $excludedClasses = []
37
    ) {
38
    }
39
40
    public function addCollector(CollectorInterface $collector): void
41
    {
42
        $this->collectors[$collector->getName()] = $collector;
43
    }
44
45 40
    public function setHistorySize(int $historySize): void
46
    {
47
        $this->historySize = $historySize;
48
    }
49
50
    public function read(string $type, ?string $id = null): array
51
    {
52 40
        clearstatcache();
53 40
        $data = [];
54 40
        $pattern = sprintf(
55 40
            '%s/**/%s/%s.json',
56 40
            $this->path,
57
            $id ?? '**',
58
            $type,
59 40
        );
60
        $dataFiles = glob($pattern, GLOB_NOSORT);
61 40
        uasort($dataFiles, static fn ($a, $b) => filemtime($a) <=> filemtime($b));
62
63
        foreach ($dataFiles as $file) {
64 8
            $dir = dirname($file);
65
            $id = substr($dir, strlen(dirname($file, 2)) + 1);
66 8
            $data[$id] = Json::decode(file_get_contents($file));
67
        }
68
69 16
        return $data;
70
    }
71 16
72 16
    public function flush(): void
73 16
    {
74 16
        $basePath = $this->path . '/' . date('Y-m-d') . '/' . $this->idGenerator->getId() . '/';
75 16
76
        try {
77 16
            FileHelper::ensureDirectory($basePath);
78
79
            $dumper = Dumper::create($this->getData(), $this->excludedClasses);
80
            file_put_contents($basePath . self::TYPE_DATA . '.json', $dumper->asJson(30));
81
            file_put_contents($basePath . self::TYPE_OBJECTS . '.json', $dumper->asJsonObjectsMap(30));
82
83 16
            $summaryData = Dumper::create($this->collectSummaryData())->asJson();
84
            file_put_contents($basePath . self::TYPE_SUMMARY . '.json', $summaryData);
85
        } finally {
86 32
            $this->collectors = [];
87
            $this->gc();
88 32
        }
89
    }
90 32
91 32
    public function getData(): array
92 32
    {
93
        return array_map(static fn (CollectorInterface $collector) => $collector->getCollected(), $this->collectors);
94 32
    }
95 32
96 32
    public function clear(): void
97
    {
98 32
        FileHelper::removeDirectory($this->path);
99 32
    }
100
101 32
    /**
102 32
     * Collects summary data of current request.
103 32
     */
104
    private function collectSummaryData(): array
105
    {
106
        $summaryData = [
107 40
            'id' => $this->idGenerator->getId(),
108
            'collectors' => array_keys($this->collectors),
109 40
        ];
110 40
111 40
        foreach ($this->collectors as $collector) {
112
            if ($collector instanceof SummaryCollectorInterface) {
113
                $summaryData = [...$summaryData, ...$collector->getSummary()];
114 40
            }
115
        }
116
117 8
        return $summaryData;
118
    }
119 8
120
    /**
121
     * Removes obsolete data files
122
     */
123
    private function gc(): void
124
    {
125
        $summaryFiles = glob($this->path . '/**/**/summary.json', GLOB_NOSORT);
126
        if (empty($summaryFiles) || count($summaryFiles) <= $this->historySize) {
127 32
            return;
128
        }
129 32
130 32
        uasort($summaryFiles, static fn ($a, $b) => filemtime($b) <=> filemtime($a));
131 32
        $excessFiles = array_slice($summaryFiles, $this->historySize);
132
        foreach ($excessFiles as $file) {
133
            $path1 = dirname($file);
134 32
            $path2 = dirname($file, 2);
135 32
            $path3 = dirname($file, 3);
136
            $resource = substr($path1, strlen($path3));
137
138
            FileHelper::removeDirectory($this->path . $resource);
139
140 32
            // Clean empty group directories
141
            $group = substr($path2, strlen($path3));
142
            if (FileHelper::isEmptyDirectory($this->path . $group)) {
143
                FileHelper::removeDirectory($this->path . $group);
144
            }
145
        }
146
    }
147
}
148