|
2 | 2 |
|
3 | 3 | namespace PHPStan\File; |
4 | 4 |
|
5 | | -use PHPStan\DependencyInjection\AutowiredParameter; |
6 | | -use PHPStan\DependencyInjection\AutowiredService; |
7 | | -use PHPStan\ShouldNotHappenException; |
8 | | -use function array_diff; |
9 | | -use function array_key_exists; |
10 | | -use function array_keys; |
11 | | -use function array_merge; |
12 | | -use function array_unique; |
13 | | -use function hash_file; |
14 | | -use function is_dir; |
15 | | -use function is_file; |
16 | | - |
17 | | -#[AutowiredService] |
18 | | -final class FileMonitor |
| 5 | +/** |
| 6 | + * Watches the analysed and scanned files for changes between PHPStan Pro analyses. |
| 7 | + * |
| 8 | + * {@see HashingFileMonitor} is the portable implementation: it re-hashes every |
| 9 | + * monitored file on every poll. The native implementations |
| 10 | + * ({@see FsEventsFileMonitor}, {@see InotifyFileMonitor}) wrap it and let the |
| 11 | + * kernel answer "did anything change at all", so an idle poll touches no files; |
| 12 | + * once the kernel says yes, they delegate to the hashing monitor so the reported |
| 13 | + * result is identical either way. |
| 14 | + * |
| 15 | + * {@see FileMonitorFactory} picks the implementation for the current platform |
| 16 | + * and hands it back initialized. |
| 17 | + */ |
| 18 | +interface FileMonitor |
19 | 19 | { |
20 | 20 |
|
21 | | - /** @var array<string, string>|null */ |
22 | | - private ?array $fileHashes = null; |
23 | | - |
24 | | - /** @var array<string>|null */ |
25 | | - private ?array $filePaths = null; |
26 | | - |
27 | | - /** |
28 | | - * @param string[] $analysedPaths |
29 | | - * @param string[] $analysedPathsFromConfig |
30 | | - * @param string[] $scanFiles |
31 | | - * @param string[] $scanDirectories |
32 | | - */ |
33 | | - public function __construct( |
34 | | - #[AutowiredParameter(ref: '@fileFinderAnalyse')] |
35 | | - private FileFinder $analyseFileFinder, |
36 | | - #[AutowiredParameter(ref: '@fileFinderScan')] |
37 | | - private FileFinder $scanFileFinder, |
38 | | - #[AutowiredParameter] |
39 | | - private array $analysedPaths, |
40 | | - #[AutowiredParameter] |
41 | | - private array $analysedPathsFromConfig, |
42 | | - #[AutowiredParameter] |
43 | | - private array $scanFiles, |
44 | | - #[AutowiredParameter] |
45 | | - private array $scanDirectories, |
46 | | - private DirectoryWalker $directoryWalker, |
47 | | - ) |
48 | | - { |
49 | | - } |
| 21 | + public function getChanges(): FileMonitorResult; |
50 | 22 |
|
51 | 23 | /** |
52 | | - * @param array<string> $filePaths |
| 24 | + * How often the caller should poll. A monitor whose idle poll is free can |
| 25 | + * afford a much shorter interval, which is what makes an edit noticed sooner. |
53 | 26 | */ |
54 | | - public function initialize(array $filePaths): void |
55 | | - { |
56 | | - $finderResult = $this->analyseFileFinder->findFiles($this->analysedPaths); |
57 | | - $fileHashes = []; |
58 | | - foreach (array_unique(array_merge($finderResult->getFiles(), $filePaths, $this->getScannedFiles($finderResult->getFiles()))) as $filePath) { |
59 | | - $fileHashes[$filePath] = $this->getFileHash($filePath); |
60 | | - } |
61 | | - |
62 | | - $this->fileHashes = $fileHashes; |
63 | | - $this->filePaths = $filePaths; |
64 | | - } |
65 | | - |
66 | | - public function getChanges(): FileMonitorResult |
67 | | - { |
68 | | - if ($this->fileHashes === null || $this->filePaths === null) { |
69 | | - throw new ShouldNotHappenException(); |
70 | | - } |
71 | | - |
72 | | - // Detecting a change means looking at the filesystem as it is now, so the walks shared |
73 | | - // with the analysis file discovery must not be reused here. |
74 | | - $this->directoryWalker->clearCachedWalks(); |
75 | | - $finderResult = $this->analyseFileFinder->findFiles($this->analysedPaths); |
76 | | - $oldFileHashes = $this->fileHashes; |
77 | | - $fileHashes = []; |
78 | | - $newFiles = []; |
79 | | - $changedFiles = []; |
80 | | - $deletedFiles = []; |
81 | | - $filePaths = array_unique(array_merge($finderResult->getFiles(), $this->filePaths, $this->getScannedFiles($finderResult->getFiles()))); |
82 | | - foreach ($filePaths as $filePath) { |
83 | | - if (!array_key_exists($filePath, $oldFileHashes)) { |
84 | | - $newFiles[] = $filePath; |
85 | | - $fileHashes[$filePath] = $this->getFileHash($filePath); |
86 | | - continue; |
87 | | - } |
88 | | - |
89 | | - $oldHash = $oldFileHashes[$filePath]; |
90 | | - unset($oldFileHashes[$filePath]); |
91 | | - $newHash = $this->getFileHash($filePath); |
92 | | - $fileHashes[$filePath] = $newHash; |
93 | | - if ($oldHash === $newHash) { |
94 | | - continue; |
95 | | - } |
96 | | - |
97 | | - $changedFiles[] = $filePath; |
98 | | - } |
99 | | - |
100 | | - $this->fileHashes = $fileHashes; |
101 | | - |
102 | | - foreach (array_keys($oldFileHashes) as $file) { |
103 | | - $deletedFiles[] = $file; |
104 | | - } |
105 | | - |
106 | | - return new FileMonitorResult( |
107 | | - $newFiles, |
108 | | - $changedFiles, |
109 | | - $deletedFiles, |
110 | | - ); |
111 | | - } |
112 | | - |
113 | | - private function getFileHash(string $filePath): string |
114 | | - { |
115 | | - $hash = hash_file('sha256', $filePath); |
116 | | - |
117 | | - if ($hash === false) { |
118 | | - throw new CouldNotReadFileException($filePath); |
119 | | - } |
120 | | - |
121 | | - return $hash; |
122 | | - } |
123 | | - |
124 | | - /** |
125 | | - * @param string[] $allAnalysedFiles |
126 | | - * @return array<string> |
127 | | - */ |
128 | | - private function getScannedFiles(array $allAnalysedFiles): array |
129 | | - { |
130 | | - $scannedFiles = $this->scanFiles; |
131 | | - $analysedDirectories = []; |
132 | | - foreach (array_merge($this->analysedPaths, $this->analysedPathsFromConfig) as $analysedPath) { |
133 | | - if (is_file($analysedPath)) { |
134 | | - continue; |
135 | | - } |
136 | | - |
137 | | - if (!is_dir($analysedPath)) { |
138 | | - continue; |
139 | | - } |
140 | | - |
141 | | - $analysedDirectories[] = $analysedPath; |
142 | | - } |
143 | | - |
144 | | - $directories = array_unique(array_merge($analysedDirectories, $this->scanDirectories)); |
145 | | - foreach ($this->scanFileFinder->findFiles($directories)->getFiles() as $file) { |
146 | | - $scannedFiles[] = $file; |
147 | | - } |
148 | | - |
149 | | - return array_diff($scannedFiles, $allAnalysedFiles); |
150 | | - } |
| 27 | + public function getPollInterval(): float; |
151 | 28 |
|
152 | 29 | } |
0 commit comments