@@ -42,9 +42,9 @@ final class StreamWrapper implements StreamWrapperInterface
4242 private static string $ cacheDir = '' ;
4343
4444 /**
45- * In-memory cache for url_stat to avoid 100,000+ unregister/register cycles on file_exists/is_file .
45+ * In-memory cache for positive url_stat results .
4646 *
47- * @var array<string, array<int|string, int>|false >
47+ * @var array<string, array<int|string, int>>
4848 */
4949 private static array $ statCache = [];
5050
@@ -338,13 +338,14 @@ public function stream_close(): void
338338
339339 /**
340340 * High-speed stat resolution with $O(1)$ memoization cache.
341+ * Never caches false (negative lookups) so newly created directories and files are immediately discovered.
341342 *
342343 * @return array<int|string, int>|false
343344 */
344345 public function url_stat (string $ path , int $ flags ): array |false
345346 {
346347 $ normalized = str_replace ('\\' , '/ ' , $ path );
347- if (\array_key_exists ( $ normalized , self ::$ statCache )) {
348+ if (isset ( self ::$ statCache[ $ normalized ] )) {
348349 return self ::$ statCache [$ normalized ];
349350 }
350351
@@ -353,11 +354,19 @@ public function url_stat(string $path, int $flags): array|false
353354 $ result = self ::silent (fn () => stat ($ path ));
354355 self ::register ();
355356
356- return self ::$ statCache [$ normalized ] = $ result ;
357+ // Only cache positive results (existing files/dirs)
358+ if ($ result !== false ) {
359+ self ::$ statCache [$ normalized ] = $ result ;
360+ }
361+
362+ return $ result ;
357363 }
358364
359365 public function stream_metadata (string $ path , int $ option , mixed $ value ): bool
360366 {
367+ $ normalized = str_replace ('\\' , '/ ' , $ path );
368+ unset(self ::$ statCache [$ normalized ]);
369+
361370 self ::unregister ();
362371 $ result = false ;
363372 if ($ option === STREAM_META_TOUCH ) {
@@ -419,6 +428,9 @@ public function dir_closedir(): bool
419428
420429 public function mkdir (string $ path , int $ mode , int $ options ): bool
421430 {
431+ $ normalized = str_replace ('\\' , '/ ' , $ path );
432+ unset(self ::$ statCache [$ normalized ]);
433+
422434 self ::unregister ();
423435 $ result = (bool ) self ::silent (fn () => mkdir ($ path , $ mode , (bool ) ($ options & STREAM_MKDIR_RECURSIVE )));
424436 self ::register ();
@@ -428,6 +440,9 @@ public function mkdir(string $path, int $mode, int $options): bool
428440
429441 public function rmdir (string $ path , int $ options ): bool
430442 {
443+ $ normalized = str_replace ('\\' , '/ ' , $ path );
444+ unset(self ::$ statCache [$ normalized ]);
445+
431446 self ::unregister ();
432447 $ result = (bool ) self ::silent (fn () => rmdir ($ path ));
433448 self ::register ();
0 commit comments