|
10 | 10 | use Flow\Filesystem\Exception\{InvalidArgumentException, InvalidSchemeException, RuntimeException}; |
11 | 11 | use Flow\Filesystem\Path\Filter\OnlyFiles; |
12 | 12 | use Flow\Filesystem\Stream\{NativeLocalDestinationStream, NativeLocalSourceStream}; |
13 | | -use Webmozart\Glob\Iterator\GlobIterator; |
| 13 | +use Webmozart\Glob\Glob; |
| 14 | +use Webmozart\Glob\Iterator\{GlobFilterIterator, GlobIterator}; |
14 | 15 |
|
15 | 16 | /** |
16 | 17 | * This implementation is based on the native PHP filesystem functions documented here: https://www.php.net/manual/en/book.filesystem.php |
@@ -129,7 +130,7 @@ public function rm(Path $path) : bool |
129 | 130 |
|
130 | 131 | $deletedCount = 0; |
131 | 132 |
|
132 | | - foreach (new GlobIterator($path->path()) as $filePath) { |
| 133 | + foreach ($this->matchChildFirst($path->path()) as $filePath) { |
133 | 134 | $filePath = type_string()->assert($filePath); |
134 | 135 |
|
135 | 136 | if (\is_dir($filePath)) { |
@@ -188,6 +189,38 @@ public function writeTo(Path $path) : DestinationStream |
188 | 189 | return NativeLocalDestinationStream::openBlank($path); |
189 | 190 | } |
190 | 191 |
|
| 192 | + /** |
| 193 | + * Lazy iterator over glob matches in CHILD_FIRST order so callers can safely delete each match |
| 194 | + * without confusing webmozart/glob's internal RecursiveIteratorIterator (which descends with SELF_FIRST). |
| 195 | + */ |
| 196 | + private function matchChildFirst(string $glob) : \Iterator |
| 197 | + { |
| 198 | + $glob = self::canonicalizePath($glob); |
| 199 | + $basePath = Glob::getBasePath($glob); |
| 200 | + |
| 201 | + if (!\is_dir($basePath)) { |
| 202 | + return new \EmptyIterator(); |
| 203 | + } |
| 204 | + |
| 205 | + $recursive = new \RecursiveIteratorIterator( |
| 206 | + new \RecursiveDirectoryIterator( |
| 207 | + $basePath, |
| 208 | + \RecursiveDirectoryIterator::CURRENT_AS_PATHNAME | \RecursiveDirectoryIterator::SKIP_DOTS |
| 209 | + ), |
| 210 | + \RecursiveIteratorIterator::CHILD_FIRST |
| 211 | + ); |
| 212 | + |
| 213 | + return new GlobFilterIterator( |
| 214 | + $glob, |
| 215 | + (static function () use ($recursive) { |
| 216 | + foreach ($recursive as $path) { |
| 217 | + yield self::canonicalizePath(type_string()->assert($path)); |
| 218 | + } |
| 219 | + })(), |
| 220 | + GlobFilterIterator::FILTER_VALUE |
| 221 | + ); |
| 222 | + } |
| 223 | + |
191 | 224 | private function rmdir(string $dirPath) : void |
192 | 225 | { |
193 | 226 | if (!\is_dir($dirPath)) { |
@@ -221,13 +254,21 @@ private function rmdir(string $dirPath) : void |
221 | 254 | \rmdir($dirPath); |
222 | 255 | } |
223 | 256 |
|
| 257 | + private static function canonicalizePath(string $path) : string |
| 258 | + { |
| 259 | + return type_string()->cast(\preg_replace('#/+#', '/', \str_replace('\\', '/', $path))); |
| 260 | + } |
| 261 | + |
224 | 262 | private static function statFor(Path $path, string $absolutePath) : FileStatus |
225 | 263 | { |
226 | 264 | $isFile = \is_file($absolutePath); |
227 | | - $size = $isFile ? (\filesize($absolutePath) ?: null) : null; |
228 | 265 | $mtime = \filemtime($absolutePath); |
229 | | - $lastModifiedAt = $mtime !== false ? new \DateTimeImmutable('@' . $mtime) : null; |
230 | 266 |
|
231 | | - return new FileStatus($path, $isFile, $size, $lastModifiedAt); |
| 267 | + return new FileStatus( |
| 268 | + $path, |
| 269 | + $isFile, |
| 270 | + $isFile ? (\filesize($absolutePath) ?: null) : null, |
| 271 | + $mtime !== false ? new \DateTimeImmutable('@' . $mtime) : null |
| 272 | + ); |
232 | 273 | } |
233 | 274 | } |
0 commit comments