タイトル: ファイルのアップロードとファイルの名前
SEOタイトル: djangoにおけるファイルのアップロードとファイル名および拡張子の取得
この本文には実行可能なタグ等が含まれるため、安全のためHTMLソースとして表示しています。本文データは変更していません。
<table border="1" cellpadding="1" cellspacing="1" style="width:1000px">
<thead><tr><th style="background-color:#f39c12;color:#ffffff;text-align:left">この記事の要点</th></tr></thead>
<tbody><tr><td style="background-color:#fef9e7"><ul>
<li>Laravel でファイルアップロード処理</li>
<li><strong>$request->file('fieldname')</strong>: UploadedFile オブジェクト取得</li>
<li><strong>$file->store('dir')</strong>: 自動ファイル名で保存</li>
<li><strong>$file->storeAs('dir', 'name.jpg')</strong>: 名前指定で保存</li>
<li><strong>$file->getClientOriginalName()</strong>: アップロード時のファイル名</li>
</ul></td></tr></tbody>
</table>
<p> </p>
<h2>HTML フォーム</h2>
<pre style="background:#0f172a;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;font-family:Consolas,Monaco,monospace;font-size:0.9em;line-height:1.5"><code><form action="/upload" method="POST" enctype="multipart/form-data">
@csrf
<input type="file" name="image" accept="image/*">
<input type="file" name="documents[]" multiple>
<button type="submit">アップロード</button>
</form>
<!-- 必須: enctype="multipart/form-data" --></code></pre>
<h2>コントローラ</h2>
<pre style="background:#0f172a;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;font-family:Consolas,Monaco,monospace;font-size:0.9em;line-height:1.5"><code>public function upload(Request $request)
{
// バリデーション
$request->validate([
'image' => 'required|image|mimes:jpg,png,gif|max:2048', // 2MB
'documents.*' => 'required|file|mimes:pdf,doc,docx|max:10240',
]);
// ファイル取得
$file = $request->file('image');
// 自動ファイル名で保存
$path = $file->store('uploads');
// → storage/app/uploads/abc123def456.jpg のような名前
// public ディスクに保存(Web 公開)
$path = $file->store('uploads', 'public');
// → storage/app/public/uploads/abc...
// → アクセス URL: /storage/uploads/abc... (storage:link 必要)
// 名前指定で保存
$path = $file->storeAs('uploads', 'my-file.jpg', 'public');
return back()->with('success', "保存先: $path");
}</code></pre>
<h2>UploadedFile の主要メソッド</h2>
<pre style="background:#0f172a;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;font-family:Consolas,Monaco,monospace;font-size:0.9em;line-height:1.5"><code>$file = $request->file('image');
// アップロード時の情報
$file->getClientOriginalName(); // "my-photo.jpg"
$file->getClientOriginalExtension(); // "jpg"
$file->getClientMimeType(); // "image/jpeg"
$file->getSize(); // バイト数
// サーバ上の情報
$file->getRealPath(); // 一時ファイルの絶対パス
$file->getMimeType(); // 実際の MIME (検知)
$file->guessExtension(); // 推定拡張子 (jpeg, png 等)
$file->hashName(); // ユニークなハッシュ名
// 保存
$file->store('dir'); // 自動名
$file->store('dir', 'disk'); // ディスク指定
$file->storeAs('dir', 'name.jpg'); // 名前指定
$file->storeAs('dir', 'name.jpg', 'public');
$file->storePublicly('dir'); // public ディスク
// 任意の場所に直接保存
$file->move(public_path('images'), 'photo.jpg');</code></pre>
<h2>ファイル名のカスタマイズ</h2>
<pre style="background:#0f172a;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;font-family:Consolas,Monaco,monospace;font-size:0.9em;line-height:1.5"><code>// パターン 1: 元の名前を保持
$originalName = $file->getClientOriginalName();
$path = $file->storeAs('uploads', $originalName);
// パターン 2: タイムスタンプ + 元の名前
$name = time() . '_' . $file->getClientOriginalName();
$path = $file->storeAs('uploads', $name);
// パターン 3: ハッシュベース (ユニーク保証)
$name = hash('sha256', uniqid() . $file->getClientOriginalName())
. '.' . $file->getClientOriginalExtension();
$path = $file->storeAs('uploads', $name);
// パターン 4: ユーザ別フォルダ
$userId = auth()->id();
$path = $file->storeAs("uploads/user_{$userId}", $file->getClientOriginalName());
// パターン 5: 日付別フォルダ
$dir = 'uploads/' . date('Y/m/d');
$path = $file->store($dir);
// パターン 6: スラグ化したファイル名
$name = Str::slug(pathinfo($file->getClientOriginalName(), PATHINFO_FILENAME))
. '.' . $file->getClientOriginalExtension();
$path = $file->storeAs('uploads', $name);</code></pre>
<h2>ファイル名の安全化</h2>
<pre style="background:#0f172a;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;font-family:Consolas,Monaco,monospace;font-size:0.9em;line-height:1.5"><code>// ❌ 危険: 元の名前そのまま (パストラバーサル攻撃)
$path = $file->storeAs('uploads', $_POST['name']);
// 攻撃者が "../../etc/passwd" のような名前を送ってきたら...
// ✅ 安全な対処
// 1. basename で抽出
$safeName = basename($userInput);
// 2. Str::slug で英数字のみ
$safeName = Str::slug(pathinfo($userInput, PATHINFO_FILENAME))
. '.' . pathinfo($userInput, PATHINFO_EXTENSION);
// 3. ハッシュ化で完全置換 (推奨)
$safeName = hash('sha256', uniqid()) . '.' . $file->getClientOriginalExtension();
// 4. UUID
use Illuminate\Support\Str;
$safeName = Str::uuid() . '.' . $file->getClientOriginalExtension();
// 5. 元の名前は DB の別カラムに保存
DB::table('files')->insert([
'stored_name' => $safeName,
'original_name' => $file->getClientOriginalName(),
'mime_type' => $file->getMimeType(),
'size' => $file->getSize(),
]);</code></pre>
<h2>複数ファイルアップロード</h2>
<pre style="background:#0f172a;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;font-family:Consolas,Monaco,monospace;font-size:0.9em;line-height:1.5"><code>// HTML: name="documents[]" multiple
public function uploadMultiple(Request $request)
{
$request->validate([
'documents' => 'required|array',
'documents.*' => 'required|file|mimes:pdf,jpg,png|max:10240',
]);
$paths = [];
foreach ($request->file('documents') as $file) {
$paths[] = $file->store('uploads', 'public');
}
return response()->json(['paths' => $paths]);
}</code></pre>
<h2>ストレージドライバ (S3 / GCS / ローカル)</h2>
<pre style="background:#0f172a;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;font-family:Consolas,Monaco,monospace;font-size:0.9em;line-height:1.5"><code># config/filesystems.php
'disks' => [
'local' => [
'driver' => 'local',
'root' => storage_path('app'),
],
'public' => [
'driver' => 'local',
'root' => storage_path('app/public'),
'url' => env('APP_URL').'/storage',
'visibility' => 'public',
],
's3' => [
'driver' => 's3',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION'),
'bucket' => env('AWS_BUCKET'),
],
],
// アプリ側
$path = $file->store('uploads', 's3'); // S3 にアップロード
$url = Storage::disk('s3')->url($path); // 公開 URL 取得
Storage::disk('s3')->temporaryUrl($path, now()->addHour()); // 署名付き</code></pre>
<h2>画像のリサイズ・最適化</h2>
<pre style="background:#0f172a;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;font-family:Consolas,Monaco,monospace;font-size:0.9em;line-height:1.5"><code>// Intervention Image パッケージ
// composer require intervention/image
use Intervention\Image\Facades\Image;
public function uploadImage(Request $request)
{
$file = $request->file('image');
// リサイズ
$img = Image::make($file->getRealPath());
$img->resize(800, null, function ($constraint) {
$constraint->aspectRatio();
$constraint->upsize();
});
// 保存
$path = 'uploads/' . uniqid() . '.jpg';
$img->save(storage_path("app/public/$path"), 80); // 品質 80
return back()->with('path', $path);
}</code></pre>
<h2>進捗表示 (フロント側)</h2>
<pre style="background:#0f172a;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;font-family:Consolas,Monaco,monospace;font-size:0.9em;line-height:1.5"><code>// JavaScript (axios)
const formData = new FormData();
formData.append("file", fileInput.files[0]);
axios.post("/upload", formData, {
headers: { "Content-Type": "multipart/form-data" },
onUploadProgress: (e) => {
const percent = Math.round((e.loaded * 100) / e.total);
document.getElementById("progress").value = percent;
}
}).then(response => console.log(response.data));
// または fetch + XHR
const xhr = new XMLHttpRequest();
xhr.open("POST", "/upload");
xhr.upload.addEventListener("progress", e => {
if (e.lengthComputable) {
const percent = (e.loaded / e.total) * 100;
console.log(`${percent}%`);
}
});
xhr.send(formData);</code></pre>
<h2>制限・セキュリティ</h2>
<ul>
<li><strong>容量制限</strong>: <code>php.ini</code> の <code>upload_max_filesize</code> / <code>post_max_size</code></li>
<li><strong>nginx</strong>: <code>client_max_body_size 50M;</code></li>
<li><strong>MIME 検証</strong>: <code>mimes:</code> / <code>mimetypes:</code> バリデーション</li>
<li><strong>拡張子と内容の整合性</strong>: 偽装ファイル対策(mime_content_type 等)</li>
<li><strong>ファイル名の正規化</strong>: パストラバーサル対策</li>
<li><strong>実行可能ファイル禁止</strong>: .php / .exe / .sh 等を排除</li>
<li><strong>仮想スキャン</strong>: ClamAV 等</li>
</ul>
<h2>関連記事</h2>
<ul>
<li><a href="/article/20568">CSVファイルアップロード方法(Ajax)</a></li>
<li><a href="/article/20570">CSVファイルダウンロード方法(Ajax)</a></li>
<li><a href="/article/20573">csrf_field</a></li>
</ul>