はじめに
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関数のポイントをおさらいしましょう:
- ディレクトリハンドルのポインタを先頭に戻す関数
- ディレクトリを閉じずに再読み込みができる
- 複数パス処理に便利(カウント→処理など)
- メモリ効率的なディレクトリ操作を実現
- scandirと使い分けることが重要
- ディレクトリの比較や監視に最適
- 必ずclosedir()でリソースを解放
rewinddir関数は、効率的なディレクトリ操作を実現する重要な関数です。大量のファイルを扱う際に、ぜひ活用してください!
参考リンク
この記事が役に立ったら、ぜひシェアしてください!PHPに関する他の記事もお楽しみに。
