From 1872a85e36c8233d11934836d683a23ea4a87000 Mon Sep 17 00:00:00 2001 From: Faizan Akram Dar Date: Thu, 11 Dec 2025 12:24:32 +0100 Subject: [PATCH] fix: validate files in FilesSnapshot before processing Add check to ensure each file is valid before processing. Without this change filemtime throws an error if the file doesn't exist but is part of cache, which is very common in dev environment. For production it won't matter since is_file is cached, for some reason is_file is also faster than file_exists. Symfony codebase also prefers is_file over file_exists when checking existence of php file. --- src/Cache/FilesSnapshot.php | 5 +++++ tests/Cache/FilesSnapshotTest.php | 30 +++++++++++++++++++++++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/Cache/FilesSnapshot.php b/src/Cache/FilesSnapshot.php index 7ac70861ac..afed10f0c3 100644 --- a/src/Cache/FilesSnapshot.php +++ b/src/Cache/FilesSnapshot.php @@ -7,6 +7,7 @@ use ReflectionClass; use function array_unique; +use function is_file; use function Safe\filemtime; class FilesSnapshot @@ -24,6 +25,10 @@ public static function for(array $files): self $dependencies = []; foreach (array_unique($files) as $file) { + if (! is_file($file)) { + continue; + } + $dependencies[$file] = filemtime($file); } diff --git a/tests/Cache/FilesSnapshotTest.php b/tests/Cache/FilesSnapshotTest.php index f45aa2c75e..a82baeb95a 100644 --- a/tests/Cache/FilesSnapshotTest.php +++ b/tests/Cache/FilesSnapshotTest.php @@ -69,9 +69,37 @@ public function testTracksChangesInFile(): void self::assertTrue($snapshot->changed()); } + public function testIgnoresNonExistentFiles(): void + { + $nonExistentFile = '/path/to/non/existent/file.php'; + + $snapshot = FilesSnapshot::for([$nonExistentFile]); + + self::assertFalse($snapshot->changed()); + } + + public function testIgnoresNonExistentFilesInMixedList(): void + { + $existingFile = (new \ReflectionClass(FooType::class))->getFileName(); + $nonExistentFile1 = '/path/to/non/existent/file1.php'; + $nonExistentFile2 = '/path/to/non/existent/file2.php'; + + $snapshot = FilesSnapshot::for([ + $nonExistentFile1, + $existingFile, + $nonExistentFile2, + ]); + + self::assertFalse($snapshot->changed()); + + $this->touch($existingFile); + + self::assertTrue($snapshot->changed()); + } + private function touch(string $fileName): void { touch($fileName, filemtime($fileName) + 1); clearstatcache(); } -} \ No newline at end of file +}