[PHP]rewinddir関数の使い方を徹底解説!ディレクトリハンドルの先頭復帰

PHP

はじめに

PHPでディレクトリ内のファイル一覧を取得する際、一度読み込んだ後に再度先頭から処理したいことがあります。そんな時に役立つのがrewinddir関数です。

rewinddir関数を使えば、ディレクトリハンドルのポインタを先頭に戻すことができ、ディレクトリを閉じて再度開く必要がありません。この記事では、rewinddir関数の基本から実践的な使い方まで、分かりやすく解説していきます。

rewinddir関数とは?

rewinddir関数は、ディレクトリハンドルのポインタを先頭位置に戻す関数です。opendir()で開いたディレクトリハンドルをリセットして、再度先頭からファイル一覧を読み取れるようにします。

基本的な構文

<?php
rewinddir(?resource $dir_handle = null): void
?>
  • $dir_handle: ディレクトリハンドル(opendirで取得したリソース)
    • PHP 8.0以降: nullの場合、最後にopendir()で開かれたディレクトリハンドルを使用
  • 戻り値: なし(void)

最もシンプルな使用例

<?php
$dir = opendir('.');

// 1回目: ファイル一覧を表示
echo "=== 1回目 ===\n";
while (($file = readdir($dir)) !== false) {
    echo $file . "\n";
}

// ディレクトリハンドルを先頭に戻す
rewinddir($dir);

// 2回目: 再度ファイル一覧を表示
echo "\n=== 2回目 ===\n";
while (($file = readdir($dir)) !== false) {
    echo $file . "\n";
}

closedir($dir);
?>

ディレクトリハンドルとは?

ディレクトリハンドルは、開かれたディレクトリを参照するためのリソースです。ファイルポインタと同様に、現在読み取り位置を保持しています。

<?php
$dir = opendir('/var/www/files');

// ディレクトリハンドルはリソース型
var_dump($dir);  // resource(3) of type (stream)

// ファイルを読み取るごとにポインタが進む
echo readdir($dir) . "\n";  // .
echo readdir($dir) . "\n";  // ..
echo readdir($dir) . "\n";  // file1.txt

// 先頭に戻す
rewinddir($dir);

// 再び最初から読める
echo readdir($dir) . "\n";  // .

closedir($dir);
?>

実践的な使用例

1. ファイルの複数パス処理

<?php
function processDirectoryTwice($dirPath) {
    $dir = opendir($dirPath);
    
    if (!$dir) {
        die("ディレクトリを開けません: {$dirPath}");
    }
    
    // 1回目: ファイル数をカウント
    $fileCount = 0;
    $dirCount = 0;
    
    while (($entry = readdir($dir)) !== false) {
        if ($entry === '.' || $entry === '..') {
            continue;
        }
        
        $fullPath = $dirPath . '/' . $entry;
        
        if (is_dir($fullPath)) {
            $dirCount++;
        } else {
            $fileCount++;
        }
    }
    
    echo "=== 統計情報 ===\n";
    echo "ファイル数: {$fileCount}\n";
    echo "ディレクトリ数: {$dirCount}\n\n";
    
    // ディレクトリハンドルを先頭に戻す
    rewinddir($dir);
    
    // 2回目: 実際の処理
    echo "=== ファイル一覧 ===\n";
    while (($entry = readdir($dir)) !== false) {
        if ($entry === '.' || $entry === '..') {
            continue;
        }
        
        $fullPath = $dirPath . '/' . $entry;
        $type = is_dir($fullPath) ? '[DIR]' : '[FILE]';
        $size = is_file($fullPath) ? filesize($fullPath) : 0;
        
        echo "{$type} {$entry}";
        if ($size > 0) {
            echo " (" . formatBytes($size) . ")";
        }
        echo "\n";
    }
    
    closedir($dir);
}

function formatBytes($bytes) {
    $units = ['B', 'KB', 'MB', 'GB'];
    $i = 0;
    while ($bytes >= 1024 && $i < count($units) - 1) {
        $bytes /= 1024;
        $i++;
    }
    return round($bytes, 2) . ' ' . $units[$i];
}

// 使用例
processDirectoryTwice('/var/www/uploads');
?>

2. ファイルタイプ別の分類と表示

<?php
function classifyAndDisplayFiles($dirPath) {
    $dir = opendir($dirPath);
    
    if (!$dir) {
        return false;
    }
    
    // 1回目: ファイルを分類
    $files = [
        'images' => [],
        'documents' => [],
        'archives' => [],
        'others' => []
    ];
    
    while (($entry = readdir($dir)) !== false) {
        if ($entry === '.' || $entry === '..') {
            continue;
        }
        
        $fullPath = $dirPath . '/' . $entry;
        
        if (is_file($fullPath)) {
            $ext = strtolower(pathinfo($entry, PATHINFO_EXTENSION));
            
            if (in_array($ext, ['jpg', 'jpeg', 'png', 'gif', 'webp'])) {
                $files['images'][] = $entry;
            } elseif (in_array($ext, ['pdf', 'doc', 'docx', 'txt', 'xlsx'])) {
                $files['documents'][] = $entry;
            } elseif (in_array($ext, ['zip', 'tar', 'gz', 'rar'])) {
                $files['archives'][] = $entry;
            } else {
                $files['others'][] = $entry;
            }
        }
    }
    
    // 分類結果を表示
    echo "=== ファイル分類 ===\n";
    echo "画像: " . count($files['images']) . "件\n";
    echo "ドキュメント: " . count($files['documents']) . "件\n";
    echo "アーカイブ: " . count($files['archives']) . "件\n";
    echo "その他: " . count($files['others']) . "件\n\n";
    
    // ディレクトリハンドルを先頭に戻す
    rewinddir($dir);
    
    // 2回目: 詳細情報を表示
    echo "=== 詳細一覧 ===\n";
    while (($entry = readdir($dir)) !== false) {
        if ($entry === '.' || $entry === '..' || is_dir($dirPath . '/' . $entry)) {
            continue;
        }
        
        $fullPath = $dirPath . '/' . $entry;
        $size = filesize($fullPath);
        $modified = date('Y-m-d H:i:s', filemtime($fullPath));
        
        echo "{$entry}\n";
        echo "  サイズ: " . formatBytes($size) . "\n";
        echo "  更新日時: {$modified}\n";
    }
    
    closedir($dir);
    return $files;
}

// 使用例
$classified = classifyAndDisplayFiles('./uploads');
?>

3. フィルタリングと処理

<?php
class DirectoryFilter {
    private $dir;
    private $dirPath;
    
    public function __construct($dirPath) {
        $this->dirPath = $dirPath;
        $this->dir = opendir($dirPath);
        
        if (!$this->dir) {
            throw new Exception("ディレクトリを開けません: {$dirPath}");
        }
    }
    
    /**
     * 特定の条件に一致するファイルを取得
     */
    public function filter($callback) {
        rewinddir($this->dir);
        $matches = [];
        
        while (($entry = readdir($this->dir)) !== false) {
            if ($entry === '.' || $entry === '..') {
                continue;
            }
            
            $fullPath = $this->dirPath . '/' . $entry;
            
            if ($callback($entry, $fullPath)) {
                $matches[] = $entry;
            }
        }
        
        return $matches;
    }
    
    /**
     * 拡張子でフィルタ
     */
    public function filterByExtension($extension) {
        return $this->filter(function($entry, $fullPath) use ($extension) {
            if (!is_file($fullPath)) {
                return false;
            }
            
            $ext = strtolower(pathinfo($entry, PATHINFO_EXTENSION));
            return $ext === strtolower($extension);
        });
    }
    
    /**
     * サイズでフィルタ
     */
    public function filterBySize($minSize, $maxSize = null) {
        return $this->filter(function($entry, $fullPath) use ($minSize, $maxSize) {
            if (!is_file($fullPath)) {
                return false;
            }
            
            $size = filesize($fullPath);
            
            if ($size < $minSize) {
                return false;
            }
            
            if ($maxSize !== null && $size > $maxSize) {
                return false;
            }
            
            return true;
        });
    }
    
    /**
     * 日付でフィルタ
     */
    public function filterByDate($afterDate) {
        $timestamp = strtotime($afterDate);
        
        return $this->filter(function($entry, $fullPath) use ($timestamp) {
            if (!is_file($fullPath)) {
                return false;
            }
            
            return filemtime($fullPath) >= $timestamp;
        });
    }
    
    /**
     * すべてのエントリを配列で取得
     */
    public function getAll() {
        return $this->filter(function() {
            return true;
        });
    }
    
    public function __destruct() {
        if ($this->dir) {
            closedir($this->dir);
        }
    }
}

// 使用例
try {
    $filter = new DirectoryFilter('./uploads');
    
    // PDF ファイルのみ取得
    $pdfs = $filter->filterByExtension('pdf');
    echo "PDFファイル:\n";
    foreach ($pdfs as $pdf) {
        echo "  - {$pdf}\n";
    }
    
    // 1MB以上のファイル
    $largeFiles = $filter->filterBySize(1024 * 1024);
    echo "\n1MB以上のファイル:\n";
    foreach ($largeFiles as $file) {
        echo "  - {$file}\n";
    }
    
    // 過去7日間に更新されたファイル
    $recentFiles = $filter->filterByDate('-7 days');
    echo "\n最近更新されたファイル:\n";
    foreach ($recentFiles as $file) {
        echo "  - {$file}\n";
    }
    
} catch (Exception $e) {
    echo "エラー: " . $e->getMessage();
}
?>

4. ディレクトリの比較

<?php
function compareDirectories($dir1Path, $dir2Path) {
    $dir1 = opendir($dir1Path);
    $dir2 = opendir($dir2Path);
    
    if (!$dir1 || !$dir2) {
        die("ディレクトリを開けません");
    }
    
    // ディレクトリ1のファイルを収集
    $files1 = [];
    while (($entry = readdir($dir1)) !== false) {
        if ($entry !== '.' && $entry !== '..') {
            $files1[] = $entry;
        }
    }
    
    // ディレクトリ2のファイルを収集
    $files2 = [];
    while (($entry = readdir($dir2)) !== false) {
        if ($entry !== '.' && $entry !== '..') {
            $files2[] = $entry;
        }
    }
    
    // 比較結果
    $onlyInDir1 = array_diff($files1, $files2);
    $onlyInDir2 = array_diff($files2, $files1);
    $common = array_intersect($files1, $files2);
    
    echo "=== ディレクトリ比較 ===\n";
    echo "ディレクトリ1: {$dir1Path}\n";
    echo "ディレクトリ2: {$dir2Path}\n\n";
    
    echo "共通ファイル: " . count($common) . "件\n";
    if (!empty($common)) {
        foreach ($common as $file) {
            echo "  - {$file}\n";
        }
    }
    
    echo "\nディレクトリ1のみ: " . count($onlyInDir1) . "件\n";
    if (!empty($onlyInDir1)) {
        foreach ($onlyInDir1 as $file) {
            echo "  - {$file}\n";
        }
    }
    
    echo "\nディレクトリ2のみ: " . count($onlyInDir2) . "件\n";
    if (!empty($onlyInDir2)) {
        foreach ($onlyInDir2 as $file) {
            echo "  - {$file}\n";
        }
    }
    
    // ハンドルを先頭に戻して詳細比較
    rewinddir($dir1);
    rewinddir($dir2);
    
    // 共通ファイルのサイズ比較
    echo "\n=== 共通ファイルの詳細比較 ===\n";
    foreach ($common as $file) {
        $path1 = $dir1Path . '/' . $file;
        $path2 = $dir2Path . '/' . $file;
        
        if (is_file($path1) && is_file($path2)) {
            $size1 = filesize($path1);
            $size2 = filesize($path2);
            
            echo "{$file}:\n";
            echo "  Dir1: " . formatBytes($size1) . "\n";
            echo "  Dir2: " . formatBytes($size2) . "\n";
            
            if ($size1 !== $size2) {
                echo "  ⚠ サイズが異なります\n";
            }
        }
    }
    
    closedir($dir1);
    closedir($dir2);
}

// 使用例
compareDirectories('./backup', './current');
?>

5. ディレクトリの監視と変更検出

<?php
class DirectoryMonitor {
    private $dirPath;
    private $snapshot = [];
    
    public function __construct($dirPath) {
        $this->dirPath = $dirPath;
        $this->takeSnapshot();
    }
    
    /**
     * 現在の状態のスナップショットを取得
     */
    public function takeSnapshot() {
        $dir = opendir($this->dirPath);
        
        if (!$dir) {
            throw new Exception("ディレクトリを開けません: {$this->dirPath}");
        }
        
        $this->snapshot = [];
        
        while (($entry = readdir($dir)) !== false) {
            if ($entry === '.' || $entry === '..') {
                continue;
            }
            
            $fullPath = $this->dirPath . '/' . $entry;
            
            $this->snapshot[$entry] = [
                'size' => is_file($fullPath) ? filesize($fullPath) : 0,
                'mtime' => filemtime($fullPath),
                'type' => is_dir($fullPath) ? 'dir' : 'file'
            ];
        }
        
        closedir($dir);
    }
    
    /**
     * 変更を検出
     */
    public function detectChanges() {
        $dir = opendir($this->dirPath);
        
        if (!$dir) {
            throw new Exception("ディレクトリを開けません: {$this->dirPath}");
        }
        
        $current = [];
        
        while (($entry = readdir($dir)) !== false) {
            if ($entry === '.' || $entry === '..') {
                continue;
            }
            
            $fullPath = $this->dirPath . '/' . $entry;
            
            $current[$entry] = [
                'size' => is_file($fullPath) ? filesize($fullPath) : 0,
                'mtime' => filemtime($fullPath),
                'type' => is_dir($fullPath) ? 'dir' : 'file'
            ];
        }
        
        closedir($dir);
        
        // 変更を分析
        $changes = [
            'added' => [],
            'removed' => [],
            'modified' => []
        ];
        
        // 新規追加されたファイル
        foreach ($current as $name => $info) {
            if (!isset($this->snapshot[$name])) {
                $changes['added'][] = $name;
            }
        }
        
        // 削除されたファイル
        foreach ($this->snapshot as $name => $info) {
            if (!isset($current[$name])) {
                $changes['removed'][] = $name;
            }
        }
        
        // 変更されたファイル
        foreach ($current as $name => $info) {
            if (isset($this->snapshot[$name])) {
                $old = $this->snapshot[$name];
                
                if ($info['size'] !== $old['size'] || $info['mtime'] !== $old['mtime']) {
                    $changes['modified'][] = $name;
                }
            }
        }
        
        return $changes;
    }
    
    /**
     * 変更を表示
     */
    public function displayChanges() {
        $changes = $this->detectChanges();
        
        echo "=== ディレクトリの変更検出 ===\n";
        echo "パス: {$this->dirPath}\n\n";
        
        if (!empty($changes['added'])) {
            echo "追加されたファイル:\n";
            foreach ($changes['added'] as $file) {
                echo "  + {$file}\n";
            }
        }
        
        if (!empty($changes['removed'])) {
            echo "削除されたファイル:\n";
            foreach ($changes['removed'] as $file) {
                echo "  - {$file}\n";
            }
        }
        
        if (!empty($changes['modified'])) {
            echo "変更されたファイル:\n";
            foreach ($changes['modified'] as $file) {
                echo "  * {$file}\n";
            }
        }
        
        if (empty($changes['added']) && empty($changes['removed']) && empty($changes['modified'])) {
            echo "変更はありません\n";
        }
        
        return $changes;
    }
}

// 使用例
try {
    $monitor = new DirectoryMonitor('./watched_dir');
    
    echo "初期スナップショット取得完了\n";
    echo "何か操作してください...\n";
    sleep(5);  // 実際の使用では適切な間隔で
    
    $monitor->displayChanges();
    
} catch (Exception $e) {
    echo "エラー: " . $e->getMessage();
}
?>

6. ディレクトリ統計の収集

<?php
function collectDirectoryStats($dirPath) {
    $dir = opendir($dirPath);
    
    if (!$dir) {
        return false;
    }
    
    $stats = [
        'total_files' => 0,
        'total_dirs' => 0,
        'total_size' => 0,
        'extensions' => [],
        'largest_file' => ['name' => '', 'size' => 0],
        'newest_file' => ['name' => '', 'mtime' => 0],
        'oldest_file' => ['name' => '', 'mtime' => PHP_INT_MAX]
    ];
    
    // 1回目: 基本統計を収集
    while (($entry = readdir($dir)) !== false) {
        if ($entry === '.' || $entry === '..') {
            continue;
        }
        
        $fullPath = $dirPath . '/' . $entry;
        
        if (is_dir($fullPath)) {
            $stats['total_dirs']++;
        } elseif (is_file($fullPath)) {
            $stats['total_files']++;
            $size = filesize($fullPath);
            $mtime = filemtime($fullPath);
            
            $stats['total_size'] += $size;
            
            // 拡張子別カウント
            $ext = strtolower(pathinfo($entry, PATHINFO_EXTENSION));
            if ($ext) {
                $stats['extensions'][$ext] = ($stats['extensions'][$ext] ?? 0) + 1;
            }
            
            // 最大ファイル
            if ($size > $stats['largest_file']['size']) {
                $stats['largest_file'] = ['name' => $entry, 'size' => $size];
            }
            
            // 最新ファイル
            if ($mtime > $stats['newest_file']['mtime']) {
                $stats['newest_file'] = ['name' => $entry, 'mtime' => $mtime];
            }
            
            // 最古ファイル
            if ($mtime < $stats['oldest_file']['mtime']) {
                $stats['oldest_file'] = ['name' => $entry, 'mtime' => $mtime];
            }
        }
    }
    
    // ディレクトリハンドルを先頭に戻す
    rewinddir($dir);
    
    // 2回目: 詳細情報を表示
    echo "=== ディレクトリ統計 ===\n";
    echo "パス: {$dirPath}\n";
    echo "ファイル数: {$stats['total_files']}\n";
    echo "ディレクトリ数: {$stats['total_dirs']}\n";
    echo "合計サイズ: " . formatBytes($stats['total_size']) . "\n\n";
    
    echo "拡張子別ファイル数:\n";
    arsort($stats['extensions']);
    foreach ($stats['extensions'] as $ext => $count) {
        echo "  .{$ext}: {$count}件\n";
    }
    
    echo "\n最大ファイル: {$stats['largest_file']['name']} (" . 
         formatBytes($stats['largest_file']['size']) . ")\n";
    echo "最新ファイル: {$stats['newest_file']['name']} (" . 
         date('Y-m-d H:i:s', $stats['newest_file']['mtime']) . ")\n";
    echo "最古ファイル: {$stats['oldest_file']['name']} (" . 
         date('Y-m-d H:i:s', $stats['oldest_file']['mtime']) . ")\n\n";
    
    // 3回目: 全ファイル一覧
    rewinddir($dir);
    
    echo "=== ファイル一覧 ===\n";
    while (($entry = readdir($dir)) !== false) {
        if ($entry === '.' || $entry === '..') {
            continue;
        }
        
        $fullPath = $dirPath . '/' . $entry;
        $type = is_dir($fullPath) ? '[DIR]' : '[FILE]';
        
        echo "{$type} {$entry}\n";
    }
    
    closedir($dir);
    return $stats;
}

// 使用例
collectDirectoryStats('./uploads');
?>

rewinddir vs scandir の違い

関数戻り値メモリ複数回読み込み用途
opendir() + readdir()エントリごと効率的rewinddir()で可能大量ファイル、逐次処理
scandir()全エントリの配列全体をメモリに配列を再利用少量ファイル、配列操作
<?php
// scandir: シンプルだがメモリを多く使う
$files = scandir('.');
foreach ($files as $file) {
    echo $file . "\n";
}

// opendir + readdir: メモリ効率的
$dir = opendir('.');
while (($file = readdir($dir)) !== false) {
    echo $file . "\n";
}
closedir($dir);
?>

まとめ

rewinddir関数のポイントをおさらいしましょう:

  1. ディレクトリハンドルのポインタを先頭に戻す関数
  2. ディレクトリを閉じずに再読み込みができる
  3. 複数パス処理に便利(カウント→処理など)
  4. メモリ効率的なディレクトリ操作を実現
  5. scandirと使い分けることが重要
  6. ディレクトリの比較や監視に最適
  7. 必ずclosedir()でリソースを解放

rewinddir関数は、効率的なディレクトリ操作を実現する重要な関数です。大量のファイルを扱う際に、ぜひ活用してください!

参考リンク


この記事が役に立ったら、ぜひシェアしてください!PHPに関する他の記事もお楽しみに。

タイトルとURLをコピーしました