[PHP]rewind関数の使い方を徹底解説!ファイルポインタの先頭復帰

PHP

はじめに

PHPでファイル操作を行う際、ファイルを読み込んだ後に再度先頭から読み直したいことがよくあります。そんな時に便利なのがrewind関数です。

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

rewind関数とは?

rewind関数は、ファイルポインタを先頭位置に戻す関数です。fopen()で開いたファイルのポインタ位置をリセットして、再度先頭から読み込みや書き込みができるようにします。

基本的な構文

<?php
rewind(resource $stream): bool
?>
  • $stream: ファイルポインタ(fopenで取得したリソース)
  • 戻り値: 成功時はtrue、失敗時はfalse

最もシンプルな使用例

<?php
$file = fopen('data.txt', 'r');

// ファイルを最後まで読み込む
while (!feof($file)) {
    echo fgets($file);
}

// ポインタを先頭に戻す
rewind($file);

// 再度ファイルを読み込む
echo "\n--- 2回目の読み込み ---\n";
while (!feof($file)) {
    echo fgets($file);
}

fclose($file);
?>

ファイルポインタとは?

ファイルポインタは、ファイル内の「現在位置」を示すものです。ファイルを読み込んだり書き込んだりすると、ポインタは自動的に移動します。

<?php
$file = fopen('sample.txt', 'r');

echo "初期位置: " . ftell($file) . "\n";  // 0

fgets($file);  // 1行読み込む
echo "1行読み込み後: " . ftell($file) . "\n";  // 位置が進む

rewind($file);  // 先頭に戻す
echo "rewind後: " . ftell($file) . "\n";  // 0

fclose($file);
?>

実践的な使用例

1. ファイルの複数回読み込み

<?php
function readFileTwice($filename) {
    $file = fopen($filename, 'r');
    
    if (!$file) {
        die("ファイルを開けません: {$filename}");
    }
    
    // 1回目の読み込み
    echo "=== 1回目 ===\n";
    while (($line = fgets($file)) !== false) {
        echo $line;
    }
    
    // ポインタを先頭に戻す
    rewind($file);
    
    // 2回目の読み込み
    echo "\n=== 2回目 ===\n";
    while (($line = fgets($file)) !== false) {
        echo $line;
    }
    
    fclose($file);
}

// 使用例
readFileTwice('data.txt');
?>

2. CSVファイルのヘッダーとデータの分離処理

<?php
function processCSV($filename) {
    $file = fopen($filename, 'r');
    
    if (!$file) {
        return false;
    }
    
    // ヘッダー行を取得
    $headers = fgetcsv($file);
    echo "ヘッダー: " . implode(', ', $headers) . "\n\n";
    
    // ポインタを先頭に戻す
    rewind($file);
    
    // ヘッダーをスキップして全データを処理
    fgetcsv($file);  // ヘッダーをスキップ
    
    $data = [];
    while (($row = fgetcsv($file)) !== false) {
        $data[] = array_combine($headers, $row);
    }
    
    fclose($file);
    return $data;
}

// 使用例
$users = processCSV('users.csv');
print_r($users);
?>

3. ファイルの行数カウントと内容読み込み

<?php
function readFileWithLineCount($filename) {
    $file = fopen($filename, 'r');
    
    if (!$file) {
        return false;
    }
    
    // まず行数をカウント
    $lineCount = 0;
    while (fgets($file) !== false) {
        $lineCount++;
    }
    
    echo "総行数: {$lineCount}\n\n";
    
    // ポインタを先頭に戻して内容を読み込む
    rewind($file);
    
    $lineNumber = 1;
    while (($line = fgets($file)) !== false) {
        echo sprintf("%3d: %s", $lineNumber, $line);
        $lineNumber++;
    }
    
    fclose($file);
}

// 使用例
readFileWithLineCount('log.txt');
?>

4. 一時ファイルでのデータ処理

<?php
function processTempData() {
    // 一時ファイルを作成
    $temp = tmpfile();
    
    // データを書き込む
    fwrite($temp, "Name,Age,City\n");
    fwrite($temp, "Alice,30,Tokyo\n");
    fwrite($temp, "Bob,25,Osaka\n");
    fwrite($temp, "Charlie,35,Kyoto\n");
    
    // ポインタを先頭に戻す
    rewind($temp);
    
    // データを読み込む
    echo "=== 一時ファイルの内容 ===\n";
    while (($line = fgets($temp)) !== false) {
        echo $line;
    }
    
    // 一時ファイルは自動的に削除される
    fclose($temp);
}

// 使用例
processTempData();
?>

5. ログファイルの検索と再読み込み

<?php
function searchAndReread($filename, $searchTerm) {
    $file = fopen($filename, 'r');
    
    if (!$file) {
        return false;
    }
    
    $found = false;
    $lineNumber = 0;
    
    // 検索語を含む行を探す
    echo "検索中: '{$searchTerm}'\n";
    while (($line = fgets($file)) !== false) {
        $lineNumber++;
        if (stripos($line, $searchTerm) !== false) {
            echo "見つかりました({$lineNumber}行目): {$line}";
            $found = true;
        }
    }
    
    if (!$found) {
        echo "見つかりませんでした\n";
        fclose($file);
        return false;
    }
    
    // ポインタを先頭に戻して全体を表示
    echo "\n=== ファイル全体 ===\n";
    rewind($file);
    
    $lineNumber = 1;
    while (($line = fgets($file)) !== false) {
        $prefix = stripos($line, $searchTerm) !== false ? '>>> ' : '    ';
        echo "{$prefix}{$lineNumber}: {$line}";
        $lineNumber++;
    }
    
    fclose($file);
    return true;
}

// 使用例
searchAndReread('access.log', 'ERROR');
?>

6. データの検証と処理

<?php
class FileValidator {
    private $file;
    private $errors = [];
    
    public function __construct($filename) {
        $this->file = fopen($filename, 'r');
        
        if (!$this->file) {
            throw new Exception("ファイルを開けません: {$filename}");
        }
    }
    
    public function validate() {
        $this->errors = [];
        $lineNumber = 0;
        
        while (($line = fgets($this->file)) !== false) {
            $lineNumber++;
            
            // 空行のチェック
            if (trim($line) === '') {
                $this->errors[] = "行 {$lineNumber}: 空行があります";
            }
            
            // 文字数チェック(例: 100文字以下)
            if (strlen($line) > 100) {
                $this->errors[] = "行 {$lineNumber}: 100文字を超えています";
            }
        }
        
        return empty($this->errors);
    }
    
    public function process() {
        if (!$this->validate()) {
            echo "検証エラー:\n";
            foreach ($this->errors as $error) {
                echo "  - {$error}\n";
            }
            return false;
        }
        
        // 検証成功後、ポインタを先頭に戻して処理
        rewind($this->file);
        
        echo "=== ファイル処理開始 ===\n";
        $lineNumber = 1;
        while (($line = fgets($this->file)) !== false) {
            // データ処理
            echo "処理中 ({$lineNumber}): " . trim($line) . "\n";
            $lineNumber++;
        }
        
        return true;
    }
    
    public function __destruct() {
        if ($this->file) {
            fclose($this->file);
        }
    }
}

// 使用例
try {
    $validator = new FileValidator('data.txt');
    $validator->process();
} catch (Exception $e) {
    echo "エラー: " . $e->getMessage();
}
?>

7. メモリ効率的な大容量ファイル処理

<?php
function processLargeFile($filename, $chunkSize = 1000) {
    $file = fopen($filename, 'r');
    
    if (!$file) {
        return false;
    }
    
    $totalLines = 0;
    $chunks = [];
    
    // チャンクごとに処理
    $chunkNumber = 0;
    while (!feof($file)) {
        $chunk = [];
        
        for ($i = 0; $i < $chunkSize && !feof($file); $i++) {
            $line = fgets($file);
            if ($line !== false) {
                $chunk[] = $line;
                $totalLines++;
            }
        }
        
        if (!empty($chunk)) {
            $chunkNumber++;
            echo "チャンク {$chunkNumber}: " . count($chunk) . " 行処理\n";
            
            // チャンクごとの処理(例: 統計情報の収集)
            $chunks[] = [
                'number' => $chunkNumber,
                'lines' => count($chunk),
                'avg_length' => array_sum(array_map('strlen', $chunk)) / count($chunk)
            ];
        }
    }
    
    echo "\n=== 処理完了 ===\n";
    echo "総行数: {$totalLines}\n";
    echo "総チャンク数: {$chunkNumber}\n\n";
    
    // 特定のチャンクを再読み込み(例: 最初のチャンク)
    echo "=== 最初のチャンクを再読み込み ===\n";
    rewind($file);
    
    for ($i = 0; $i < $chunkSize && !feof($file); $i++) {
        $line = fgets($file);
        if ($line !== false) {
            echo $line;
        }
    }
    
    fclose($file);
    return $chunks;
}

// 使用例
processLargeFile('large_log.txt', 100);
?>

rewind vs fseek の違い

似た機能を持つfseek関数との違いを理解しましょう:

関数機能移動先柔軟性
rewind()先頭に戻す常に先頭(位置0)固定
fseek()任意の位置に移動指定した位置高い
<?php
$file = fopen('data.txt', 'r');

// rewind: 常に先頭(位置0)に戻す
rewind($file);
echo "位置: " . ftell($file) . "\n";  // 0

// fseek: 任意の位置に移動可能
fseek($file, 10);  // 位置10に移動
echo "位置: " . ftell($file) . "\n";  // 10

fseek($file, 0);   // 位置0に移動(rewindと同じ効果)
echo "位置: " . ftell($file) . "\n";  // 0

fclose($file);

// 使い分け
// rewind(): 単純に先頭に戻したい場合
// fseek(): 特定の位置に移動したい場合
?>

rewindとfseekの等価性

<?php
// これらは同じ動作をします
rewind($file);
fseek($file, 0);

// しかしrewindの方が意図が明確で読みやすい
?>

エラーハンドリング

<?php
function safeRewind($file) {
    if (!is_resource($file)) {
        trigger_error('無効なファイルリソースです', E_USER_WARNING);
        return false;
    }
    
    // rewindを実行
    $result = rewind($file);
    
    if ($result === false) {
        trigger_error('ファイルポインタの巻き戻しに失敗しました', E_USER_WARNING);
        return false;
    }
    
    // 位置確認
    $position = ftell($file);
    if ($position !== 0) {
        trigger_error("ポインタが先頭に戻っていません(位置: {$position})", E_USER_WARNING);
        return false;
    }
    
    return true;
}

// 使用例
$file = fopen('data.txt', 'r');

if ($file) {
    fgets($file);  // 1行読む
    
    if (safeRewind($file)) {
        echo "ファイルポインタを先頭に戻しました\n";
    } else {
        echo "エラーが発生しました\n";
    }
    
    fclose($file);
}
?>

ストリームとの使用

rewind関数は、ファイルだけでなく様々なストリームで使用できます:

<?php
// php://memory ストリームでの使用
$memory = fopen('php://memory', 'r+');

fwrite($memory, "Line 1\n");
fwrite($memory, "Line 2\n");
fwrite($memory, "Line 3\n");

// 先頭に戻して読み込む
rewind($memory);

while (($line = fgets($memory)) !== false) {
    echo $line;
}

fclose($memory);

// php://temp ストリームでの使用
$temp = fopen('php://temp', 'r+');

fwrite($temp, "Temporary data\n");

rewind($temp);

echo fread($temp, 1024);

fclose($temp);
?>

実用的な完全版サンプル

<?php
/**
 * ファイル処理ユーティリティクラス
 */
class FileProcessor {
    private $file;
    private $filename;
    
    public function __construct($filename, $mode = 'r') {
        $this->filename = $filename;
        $this->file = fopen($filename, $mode);
        
        if (!$this->file) {
            throw new Exception("ファイルを開けません: {$filename}");
        }
    }
    
    /**
     * ファイルポインタを先頭に戻す
     */
    public function reset() {
        if (!rewind($this->file)) {
            throw new Exception('ファイルポインタの巻き戻しに失敗');
        }
        
        return $this;
    }
    
    /**
     * 全行を配列として取得
     */
    public function getAllLines() {
        $this->reset();
        $lines = [];
        
        while (($line = fgets($this->file)) !== false) {
            $lines[] = rtrim($line, "\r\n");
        }
        
        return $lines;
    }
    
    /**
     * 特定の条件に一致する行を抽出
     */
    public function findLines($callback) {
        $this->reset();
        $matches = [];
        $lineNumber = 0;
        
        while (($line = fgets($this->file)) !== false) {
            $lineNumber++;
            if ($callback($line, $lineNumber)) {
                $matches[] = [
                    'line_number' => $lineNumber,
                    'content' => rtrim($line, "\r\n")
                ];
            }
        }
        
        return $matches;
    }
    
    /**
     * ファイルの統計情報を取得
     */
    public function getStats() {
        $this->reset();
        
        $stats = [
            'lines' => 0,
            'chars' => 0,
            'words' => 0,
            'max_line_length' => 0,
            'min_line_length' => PHP_INT_MAX,
            'avg_line_length' => 0
        ];
        
        while (($line = fgets($this->file)) !== false) {
            $stats['lines']++;
            $lineLength = strlen($line);
            $stats['chars'] += $lineLength;
            $stats['words'] += str_word_count($line);
            $stats['max_line_length'] = max($stats['max_line_length'], $lineLength);
            $stats['min_line_length'] = min($stats['min_line_length'], $lineLength);
        }
        
        if ($stats['lines'] > 0) {
            $stats['avg_line_length'] = round($stats['chars'] / $stats['lines'], 2);
        }
        
        if ($stats['min_line_length'] === PHP_INT_MAX) {
            $stats['min_line_length'] = 0;
        }
        
        return $stats;
    }
    
    /**
     * ファイル内容を変換
     */
    public function transform($callback) {
        $this->reset();
        $result = [];
        $lineNumber = 0;
        
        while (($line = fgets($this->file)) !== false) {
            $lineNumber++;
            $result[] = $callback($line, $lineNumber);
        }
        
        return $result;
    }
    
    /**
     * 現在のポインタ位置を取得
     */
    public function getPosition() {
        return ftell($this->file);
    }
    
    public function __destruct() {
        if ($this->file) {
            fclose($this->file);
        }
    }
}

// 使用例
try {
    $processor = new FileProcessor('sample.txt');
    
    // 統計情報を取得
    $stats = $processor->getStats();
    echo "=== ファイル統計 ===\n";
    echo "行数: {$stats['lines']}\n";
    echo "文字数: {$stats['chars']}\n";
    echo "単語数: {$stats['words']}\n";
    echo "最大行長: {$stats['max_line_length']}\n";
    echo "平均行長: {$stats['avg_line_length']}\n\n";
    
    // 特定の行を検索
    $errors = $processor->findLines(function($line) {
        return stripos($line, 'ERROR') !== false;
    });
    
    echo "=== エラー行 ===\n";
    foreach ($errors as $error) {
        echo "行 {$error['line_number']}: {$error['content']}\n";
    }
    
    // 全行を大文字に変換
    $uppercase = $processor->transform(function($line) {
        return strtoupper($line);
    });
    
    echo "\n=== 大文字変換 ===\n";
    foreach (array_slice($uppercase, 0, 5) as $line) {
        echo $line;
    }
    
} catch (Exception $e) {
    echo "エラー: " . $e->getMessage();
}
?>

まとめ

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

  1. ファイルポインタを先頭(位置0)に戻す関数
  2. ファイルを閉じずに再読み込みができる
  3. メモリ効率的なファイル処理に便利
  4. fseek($file, 0)と同じ動作だが意図が明確
  5. tmpfile()や php://memory などのストリームでも使える
  6. CSVの複数回処理、検証と処理の分離に最適
  7. 必ずエラーハンドリングを実装する

rewind関数は、ファイル操作の柔軟性を高める重要な関数です。ファイルを効率的に処理するために、ぜひ活用してください!

参考リンク


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

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