11<?php
22
3+ declare (strict_types=1 );
4+
35namespace Vectorial1024 \LaravelCacheEvict \File ;
46
57use DirectoryIterator ;
68use ErrorException ;
79use Illuminate \Contracts \Filesystem \Filesystem ;
810use Illuminate \Support \Facades \Storage ;
911use Symfony \Component \Console \Helper \ProgressBar ;
12+ use UnexpectedValueException ;
1013use Vectorial1024 \LaravelCacheEvict \AbstractEvictStrategy ;
1114use Wilderborn \Partyline \Facade as Partyline ;
1215
@@ -54,30 +57,25 @@ public function execute(): void
5457 $ progressBar = $ this ->output ->createProgressBar ();
5558 $ progressBar ->setMaxSteps (count ($ allDirs ));
5659
57- foreach ($ allDirs as $ dir ) {
58- // we will have some verbose printing for now to test this feature.
60+ // since allDir is an array with contents like "0", "0/0", "0/1", ... "1", ...
61+ // and we are trying to remove items during iteration
62+ // we should iterate it in reverse as per literation best practices
63+ // the reversal also cleanly avoids possible race conditions by aligning iteration direction across all cleaners
64+ foreach (array_reverse ($ allDirs ) as $ dir ) {
65+ // handle cache files, then delete the directory in the same place
5966 $ this ->handleCacheFilesInDirectory ($ dir );
6067 $ progressBar ->advance ();
61- // sleep(1);
68+ // it's OK if we cannot remove directories; this usually means the directory is not empty.
69+ $ localPath = $ this ->filesystem ->path ($ dir );
70+ @rmdir ($ localPath );
71+ $ this ->deletedDirs ++;
6272 }
6373
6474 $ progressBar ->finish ();
6575 unset($ progressBar );
6676 // progress bar next empty line
6777 Partyline::info ("" );
68- Partyline::info ("Expired cache files evicted; checking empty directories... " );
69- // since allDir is an array with contents like "0", "0/0", "0/1", ... "1", ...
70- // we can reverse it to effectively remove directories
71- // in theory removing directories is very fast, so no progress bars here
72- foreach (array_reverse ($ allDirs ) as $ dir ) {
73- try {
74- $ localPath = $ this ->filesystem ->path ($ dir );
75- rmdir ($ localPath );
76- $ this ->deletedDirs ++;
77- } catch (ErrorException ) {
78- // it's OK if we cannot remove directories; this usually means the directory is not empty.
79- }
80- }
78+ Partyline::info ("Expired cache files evicted. " );
8179
8280 // all is done; print some stats
8381 $ endUnix = microtime (true );
@@ -89,14 +87,23 @@ public function execute(): void
8987 Partyline::info ("Removed {$ this ->deletedDirs } empty directories. " );
9088 }
9189
92- protected function handleCacheFilesInDirectory (string $ dirName )
90+ protected function handleCacheFilesInDirectory (string $ dirName ): void
9391 {
9492 $ localPath = $ this ->filesystem ->path ($ dirName );
95- // Partyline::info("Checking $localPath...");
9693
9794 // remove files inside directory
95+ try {
96+ $ dirIter = new DirectoryIterator ($ localPath );
97+ } catch (UnexpectedValueException $ x ) {
98+ // this indicates the directory is gone
99+ // this might be caused by race conditions
100+ // this should be rare (later execution catching up to earlier run), but better be safe than sorry
101+ Partyline::warn ("Cache directory {$ dirName } was deleted earlier than expected; skipping. " );
102+ // then we have nothing to do here
103+ return ;
104+ }
98105 /** @var \SplFileInfo $fileInfo */
99- foreach (new DirectoryIterator ( $ localPath ) as $ fileInfo ) {
106+ foreach ($ dirIter as $ fileInfo ) {
100107 if ($ fileInfo ->isDot ()) {
101108 continue ;
102109 }
@@ -106,7 +113,6 @@ protected function handleCacheFilesInDirectory(string $dirName)
106113
107114 $ realPath = $ fileInfo ->getRealPath ();
108115 $ shortFileName = $ dirName . DIRECTORY_SEPARATOR . $ fileInfo ->getFilename ();
109- // Partyline::info("Checking file $realPath...");
110116 try {
111117 // read expiry
112118 // the first 10 characters form the expiry timestamp
@@ -115,10 +121,8 @@ protected function handleCacheFilesInDirectory(string $dirName)
115121 $ expiry = (int ) file_get_contents ($ realPath , length: 10 );
116122 if (time () < $ expiry ) {
117123 // not expired yet
118- // Partyline::info("Not expired");
119124 continue ;
120125 }
121- // Partyline::info("Expired");
122126 } catch (ErrorException ) {
123127 // it's OK if we cannot read the file, this can happen when e.g. the cache file is deleted by other Laravel code
124128 Partyline::warn ("Could not read details of cache file $ shortFileName; skipping. " );
0 commit comments