導入
Webアプリケーションの開発において、ユーザーからのファイルアップロード機能は定番ですが、必ず直面するのが「ファイル名の衝突」という課題です。同じ名前のファイルが既に存在する場合、そのまま保存すると既存データが上書きされてしまうリスクがあります。本稿では、再帰関数を用いて安全かつ確実に一意のファイル名を生成する方法を解説します。
基礎知識
ファイル名の重複を解決する仕組みとして、一般的には「ファイル名_1.拡張子」「ファイル名_2.拡張子」のように末尾にインクリメントした数値を付与する手法が採られます。
ここで重要なのは「ファイルが存在する限り、数字を増やして再チェックを繰り返す」という再帰的な処理です。PHPの pathinfo() 関数を活用することで、ディレクトリ、ファイル名、拡張子を安全に分解し、再構築することが可能になります。
実装/解決策
実装のポイントは、対象のファイル名が既に存在するかを file_exists() で確認し、存在するならば自身を再度呼び出す(再帰呼び出し)点です。これにより、連番がいくつになっても自動的に空いている番号を見つけることができます。
サンプルプログラム
以下のコードは、指定されたパスにファイルが存在する場合、末尾に「_1」「_2」と連番を付与して一意のパスを返却する関数です。
/
function get_unique_filename($org_path, $num = 0) {
// 1回目以降の呼び出しであれば、ファイル名の末尾に数字を付与する
if ($num > 0) {
$info = pathinfo($org_path);
// パスを再構築:ディレクトリ/ファイル名_番号.拡張子
$path = $info[‘dirname’] . ‘/’ . $info[‘filename’] . ‘_’ . $num;
if (isset($info[‘extension’])) {
$path .= ‘.’ . $info[‘extension’];
}
} else {
// 初回は元のパスをそのまま使用
$path = $org_path;
}
// ファイルが存在するか確認
if (file_exists($path)) {
// 存在する場合は番号を増やして再帰的に自身を呼び出す
return get_unique_filename($org_path, $num + 1);
} else {
// 空いていればそのパスを確定して返す
return $path;
}
}
// 使用例
$target_file = __DIR__ . ‘/uploads/data.txt’;
$safe_path = get_unique_filename($target_file);
// 実際に保存処理を行う
file_put_contents($safe_path, “保存するデータ内容”);
echo “保存先: ” . $safe_path;
?>
応用・注意点
実務における注意点として、以下の2点があります。
1. 同時書き込みの競合(Race Condition)
マルチユーザー環境では、この関数でパスを取得した直後に、別のプロセスが同名のファイルを書き込む可能性があります。厳密な運用が求められる場合は、flock() を用いて排他制御を行うか、あるいはファイル名自体を uniqid() や UUID を使ったランダム文字列にする設計も検討してください。
2. 再帰回数の制限
極めて稀ですが、連番が数千件を超えると再帰呼び出しによるオーバーヘッドが発生します。ファイル数が膨大になることが予想されるディレクトリでは、日付やユーザーIDをフォルダ名に組み込むなど、ディレクトリ構造を工夫して単一フォルダ内のファイル数を抑えるのが運用上のベストプラクティスです。