|
17 | 17 | use PHPStan\DependencyInjection\GenerateFactory; |
18 | 18 | use PHPStan\DependencyInjection\ProjectConfigHelper; |
19 | 19 | use PHPStan\File\CouldNotReadFileException; |
| 20 | +use PHPStan\File\CouldNotWriteFileException; |
20 | 21 | use PHPStan\File\FileFinder; |
21 | 22 | use PHPStan\File\FileHelper; |
22 | | -use PHPStan\File\FileWriter; |
23 | 23 | use PHPStan\Internal\ArrayHelper; |
24 | 24 | use PHPStan\Internal\ComposerHelper; |
25 | 25 | use PHPStan\PhpDoc\StubFilesProvider; |
|
36 | 36 | use function array_unique; |
37 | 37 | use function array_values; |
38 | 38 | use function count; |
| 39 | +use function error_get_last; |
39 | 40 | use function explode; |
| 41 | +use function fclose; |
| 42 | +use function fopen; |
| 43 | +use function fwrite; |
40 | 44 | use function get_loaded_extensions; |
41 | 45 | use function getenv; |
42 | 46 | use function hash_file; |
|
49 | 53 | use function sort; |
50 | 54 | use function sprintf; |
51 | 55 | use function str_starts_with; |
| 56 | +use function substr; |
52 | 57 | use function time; |
53 | 58 | use function unlink; |
54 | 59 | use function var_export; |
@@ -1153,25 +1158,89 @@ private function save( |
1153 | 1158 |
|
1154 | 1159 | $file = $this->cacheFilePath; |
1155 | 1160 |
|
1156 | | - FileWriter::write( |
1157 | | - $file, |
1158 | | - "<?php declare(strict_types = 1); |
| 1161 | + // streamed to the file section by section - building the whole |
| 1162 | + // var_export()ed contents in memory at once would take up roughly |
| 1163 | + // twice the size of the resulting file in the main process |
| 1164 | + $handle = @fopen($file, 'w'); |
| 1165 | + if ($handle === false) { |
| 1166 | + $error = error_get_last(); |
| 1167 | + throw new CouldNotWriteFileException($file, $error !== null ? $error['message'] : 'unknown cause'); |
| 1168 | + } |
| 1169 | + |
| 1170 | + try { |
| 1171 | + $this->writeToHandle($handle, $file, "<?php declare(strict_types = 1); |
1159 | 1172 |
|
1160 | 1173 | return [ |
1161 | 1174 | 'lastFullAnalysisTime' => " . var_export($lastFullAnalysisTime, true) . ", |
1162 | 1175 | 'meta' => " . var_export($meta, true) . ", |
1163 | 1176 | 'projectExtensionFiles' => " . var_export($projectExtensionFiles, true) . ", |
1164 | | - 'errorsCallback' => static function (): array { return " . var_export($errors, true) . "; }, |
1165 | | - 'locallyIgnoredErrorsCallback' => static function (): array { return " . var_export($locallyIgnoredErrors, true) . "; }, |
1166 | | - 'linesToIgnore' => " . var_export($linesToIgnore, true) . ", |
1167 | | - 'unmatchedLineIgnores' => " . var_export($unmatchedLineIgnores, true) . ", |
1168 | | - 'collectedDataCallback' => static function (): array { return " . var_export($collectedData, true) . "; }, |
1169 | | - 'dependencies' => " . var_export($invertedDependencies, true) . ", |
1170 | | - 'packageDependencies' => " . var_export($packageDependencies, true) . ", |
1171 | | - 'exportedNodesCallback' => static function (): array { return " . var_export($exportedNodes, true) . '; }, |
| 1177 | + 'errorsCallback' => static function (): array { return "); |
| 1178 | + $this->streamArrayVarExportToHandle($handle, $file, $errors); |
| 1179 | + $this->writeToHandle($handle, $file, "; }, |
| 1180 | + 'locallyIgnoredErrorsCallback' => static function (): array { return "); |
| 1181 | + $this->streamArrayVarExportToHandle($handle, $file, $locallyIgnoredErrors); |
| 1182 | + $this->writeToHandle($handle, $file, "; }, |
| 1183 | + 'linesToIgnore' => "); |
| 1184 | + $this->streamArrayVarExportToHandle($handle, $file, $linesToIgnore); |
| 1185 | + $this->writeToHandle($handle, $file, ", |
| 1186 | + 'unmatchedLineIgnores' => "); |
| 1187 | + $this->streamArrayVarExportToHandle($handle, $file, $unmatchedLineIgnores); |
| 1188 | + $this->writeToHandle($handle, $file, ", |
| 1189 | + 'collectedDataCallback' => static function (): array { return "); |
| 1190 | + $this->streamArrayVarExportToHandle($handle, $file, $collectedData); |
| 1191 | + $this->writeToHandle($handle, $file, "; }, |
| 1192 | + 'dependencies' => "); |
| 1193 | + $this->streamArrayVarExportToHandle($handle, $file, $invertedDependencies); |
| 1194 | + $this->writeToHandle($handle, $file, ", |
| 1195 | + 'packageDependencies' => "); |
| 1196 | + $this->streamArrayVarExportToHandle($handle, $file, $packageDependencies); |
| 1197 | + $this->writeToHandle($handle, $file, ", |
| 1198 | + 'exportedNodesCallback' => static function (): array { return "); |
| 1199 | + $this->streamArrayVarExportToHandle($handle, $file, $exportedNodes); |
| 1200 | + $this->writeToHandle($handle, $file, '; }, |
1172 | 1201 | ]; |
1173 | | -', |
1174 | | - ); |
| 1202 | +'); |
| 1203 | + } finally { |
| 1204 | + fclose($handle); |
| 1205 | + } |
| 1206 | + } |
| 1207 | + |
| 1208 | + /** |
| 1209 | + * @param resource $handle |
| 1210 | + */ |
| 1211 | + private function writeToHandle($handle, string $file, string $contents): void |
| 1212 | + { |
| 1213 | + if (@fwrite($handle, $contents) === false) { |
| 1214 | + $error = error_get_last(); |
| 1215 | + throw new CouldNotWriteFileException($file, $error !== null ? $error['message'] : 'unknown cause'); |
| 1216 | + } |
| 1217 | + } |
| 1218 | + |
| 1219 | + /** |
| 1220 | + * Streams the var_export() representation of an array to the file entry |
| 1221 | + * by entry, producing output byte-identical to var_export($values, true). |
| 1222 | + * |
| 1223 | + * var_export() builds the whole export in memory even when told to print it, |
| 1224 | + * so exporting a big section in one call would take up as much memory |
| 1225 | + * as the resulting file section itself. |
| 1226 | + * |
| 1227 | + * Each entry is exported wrapped in a single-entry array whose "array (\n" |
| 1228 | + * prefix and "\n)" suffix are stripped, yielding the same bytes (including |
| 1229 | + * indentation) the entry would get inside the full export. Indenting the lines |
| 1230 | + * of a standalone value export would corrupt multi-line string contents instead. |
| 1231 | + * |
| 1232 | + * @param resource $handle |
| 1233 | + * @param array<mixed> $values |
| 1234 | + */ |
| 1235 | + private function streamArrayVarExportToHandle($handle, string $file, array $values): void |
| 1236 | + { |
| 1237 | + $this->writeToHandle($handle, $file, 'array ('); |
| 1238 | + foreach ($values as $key => $value) { |
| 1239 | + $entry = var_export([$key => $value], true); |
| 1240 | + $this->writeToHandle($handle, $file, "\n" . substr($entry, 8, -2)); |
| 1241 | + } |
| 1242 | + |
| 1243 | + $this->writeToHandle($handle, $file, "\n)"); |
1175 | 1244 | } |
1176 | 1245 |
|
1177 | 1246 | /** |
|
0 commit comments