使用Flysystem时如何更简便扁平化目录?自定义方法求优化
Nice work rolling your own solution—let's look at how we can simplify this using Flysystem's built-in tools, which are designed to work across all its adapters (local, S3, etc.) instead of relying on filesystem-specific functions.
Key Notes on Your Current Implementation
Your original code only handles one level of subdirectories (since find($dir . '/*') doesn't recurse into nested folders) and uses functions that might not work with non-local Flysystem adapters. Let's fix that with a more robust, concise approach.
Using Flysystem's Native Methods
Flysystem's listContents method lets you recursively fetch all files and directories, which we can leverage to flatten the structure properly:
use League\Flysystem\FilesystemOperator; public function flattenDir(FilesystemOperator $filesystem, string $dir, ?string $destination = null) { $destination = $destination ?? $dir; // Recursively get all files in the target directory and its subfolders $allContents = $filesystem->listContents($dir, true); // First, move all files up to the destination directory foreach ($allContents as $item) { if ($item['type'] !== 'file') { continue; } $filename = basename($item['path']); $newFilePath = rtrim($destination, '/') . '/' . $filename; // Handle duplicate filenames (optional but recommended to avoid overwrites) $counter = 1; $baseFilename = $filename; while ($filesystem->fileExists($newFilePath)) { $ext = pathinfo($baseFilename, PATHINFO_EXTENSION); $nameWithoutExt = pathinfo($baseFilename, PATHINFO_FILENAME); $filename = "{$nameWithoutExt}({$counter}).{$ext}"; $newFilePath = rtrim($destination, '/') . '/' . $filename; $counter++; } $filesystem->move($item['path'], $newFilePath); } // Clean up empty subdirectories (process deepest first to avoid parent directory errors) $directories = array_filter($allContents, fn($item) => $item['type'] === 'dir'); usort($directories, fn($a, $b) => strlen($b['path']) - strlen($a['path'])); foreach ($directories as $directory) { if ($directory['path'] === $dir) { continue; } // Only delete if the directory is now empty if (empty($filesystem->listContents($directory['path']))) { $filesystem->deleteDirectory($directory['path']); } } }
Why This Works Better
- Recursive Handling:
listContents($dir, true)automatically traverses all nested subdirectories, so even deeply buried files get moved up. - Adapter Compatibility: Uses Flysystem's core methods (
move,deleteDirectory,fileExists) which work with every storage adapter (local, AWS S3, Azure Blob Storage, etc.), whereas your original code relies on functions that might only work with local filesystems. - Duplicate Protection: The optional filename conflict handling prevents accidental overwrites, a common edge case your original code doesn't address.
- Cleaner Cleanup: Sorting directories by path length (longest first) ensures we delete child directories before their parents, avoiding errors from non-empty parent folders.
Minimal Version (Skip Conflict Handling)
If you don't need to handle duplicate filenames and want a stripped-down implementation:
use League\Flysystem\FilesystemOperator; public function flattenDir(FilesystemOperator $filesystem, string $dir, ?string $destination = null) { $destination = $destination ?? $dir; // Move all files to the destination foreach ($filesystem->listContents($dir, true) as $item) { if ($item['type'] === 'file') { $filesystem->move($item['path'], rtrim($destination, '/') . '/' . basename($item['path'])); } } // Delete empty subdirectories (process deepest first) foreach (array_reverse(array_filter($filesystem->listContents($dir, true), fn($i) => $i['type'] === 'dir')) as $dirItem) { if ($dirItem['path'] !== $dir && empty($filesystem->listContents($dirItem['path']))) { $filesystem->deleteDirectory($dirItem['path']); } } }
内容的提问来源于stack exchange,提问作者Atnaize

