Passed
Push — master ( 93e91b...83695f )
by Fabien
02:05
created

CacheProcessFactory   A

Complexity

Total Complexity 15

Size/Duplication

Total Lines 140
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 47
c 1
b 0
f 0
dl 0
loc 140
rs 10
wmc 15

8 Methods

Rating   Name   Duplication   Size   Complexity  
A isCached() 0 12 2
A addToCache() 0 7 1
A onAfterAnalysis() 0 3 1
A onAfterFileAnalysis() 0 3 1
A loadCache() 0 15 3
A writeCache() 0 14 3
A __construct() 0 13 2
A createProcesses() 0 13 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Churn\Process;
6
7
use Churn\Event\Event\AfterAnalysisEvent;
8
use Churn\Event\Event\AfterFileAnalysisEvent;
9
use Churn\Event\Subscriber\AfterAnalysis;
10
use Churn\Event\Subscriber\AfterFileAnalysis;
11
use Churn\File\File;
12
use Churn\File\FileHelper;
13
use Churn\Result\Result;
14
use InvalidArgumentException;
15
use Throwable;
16
17
/**
18
 * @internal
19
 */
20
class CacheProcessFactory implements AfterAnalysis, AfterFileAnalysis, ProcessFactory
21
{
22
23
    /**
24
     * @var string The cache file path.
25
     */
26
    private $cachePath;
27
28
    /**
29
     * @var ProcessFactory Inner process factory.
30
     */
31
    private $processFactory;
32
33
    /**
34
     * @var array<string, array<mixed>> The cached data.
35
     */
36
    private $cache;
37
38
    /**
39
     * @param string $cachePath The cache file path.
40
     * @param ProcessFactory $processFactory Inner process factory.
41
     * @throws InvalidArgumentException If the path is invalid.
42
     */
43
    public function __construct(string $cachePath, ProcessFactory $processFactory)
44
    {
45
        try {
46
            FileHelper::ensureFileIsWritable($cachePath);
47
        } catch (Throwable $e) {
48
            $message = 'Invalid cache file path: ' . $e->getMessage();
49
50
            throw new InvalidArgumentException($message, $e->getCode(), $e);
51
        }
52
53
        $this->cachePath = $cachePath;
54
        $this->processFactory = $processFactory;
55
        $this->cache = $this->loadCache($cachePath);
56
    }
57
58
    /**
59
     * @param File $file File that the processes will execute on.
60
     * @return iterable<ProcessInterface> The list of processes to execute.
61
     */
62
    public function createProcesses(File $file): iterable
63
    {
64
        if (!$this->isCached($file)) {
65
            return $this->processFactory->createProcesses($file);
66
        }
67
68
        $key = $file->getFullPath();
69
70
        $countChanges = (int) $this->cache[$key][1];
71
        $cyclomaticComplexity = (int) $this->cache[$key][2];
72
        $this->cache[$key][3] = true;
73
74
        return [new PredefinedProcess($file, $countChanges, $cyclomaticComplexity)];
75
    }
76
77
    /**
78
     * @param AfterAnalysisEvent $event The event triggered when the analysis is done.
79
     */
80
    public function onAfterAnalysis(AfterAnalysisEvent $event): void
81
    {
82
        $this->writeCache();
83
    }
84
85
    /**
86
     * @param AfterFileAnalysisEvent $event The event triggered when the analysis of a file is done.
87
     */
88
    public function onAfterFileAnalysis(AfterFileAnalysisEvent $event): void
89
    {
90
        $this->addToCache($event->getResult());
91
    }
92
93
    /**
94
     * @param Result $result The result to save.
95
     */
96
    public function addToCache(Result $result): void
97
    {
98
        $path = $result->getFile()->getFullPath();
99
        $this->cache[$path][0] = $this->cache[$path][0] ?? \md5_file($path);
100
        $this->cache[$path][1] = $result->getCommits();
101
        $this->cache[$path][2] = $result->getComplexity();
102
        $this->cache[$path][3] = true;
103
    }
104
105
    /**
106
     * Write the cache in its file.
107
     */
108
    public function writeCache(): void
109
    {
110
        $data = [];
111
112
        foreach ($this->cache as $path => $values) {
113
            if (!$values[3]) {
114
                continue;
115
            }
116
117
            unset($values[3]);
118
            $data[] = \implode(',', \array_merge([$path], $values));
119
        }
120
121
        \file_put_contents($this->cachePath, \implode("\n", $data));
122
    }
123
124
    /**
125
     * @param File $file The file to process.
126
     */
127
    private function isCached(File $file): bool
128
    {
129
        $key = $file->getFullPath();
130
131
        if (!isset($this->cache[$key])) {
132
            return false;
133
        }
134
135
        $md5 = $this->cache[$key][0];
136
        $this->cache[$key][0] = $newMd5 = \md5_file($file->getFullPath());
137
138
        return $md5 === $newMd5;
139
    }
140
141
    /**
142
     * @param string $cachePath Cache file path.
143
     * @return array<string, array<mixed>>
144
     */
145
    private function loadCache(string $cachePath): array
146
    {
147
        if (!\is_file($cachePath)) {
148
            return [];
149
        }
150
151
        $cache = [];
152
153
        foreach (\file($cachePath) as $row) {
154
            $data = \explode(',', $row);
155
            $cache[$data[0]] = \array_slice($data, 1);
156
            $cache[$data[0]][3] = false;
157
        }
158
159
        return $cache;
160
    }
161
}
162