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