You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Flysystem时如何更简便扁平化目录?自定义方法求优化

Simplifying Directory Flattening with 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 04:16:43