| この記事の要点 |
- Laravel でファイルアップロード処理
- $request->file('fieldname'): UploadedFile オブジェクト取得
- $file->store('dir'): 自動ファイル名で保存
- $file->storeAs('dir', 'name.jpg'): 名前指定で保存
- $file->getClientOriginalName(): アップロード時のファイル名
|
HTML フォーム
コントローラ
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");
}
UploadedFile の主要メソッド
$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');
ファイル名のカスタマイズ
// パターン 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);
ファイル名の安全化
// ❌ 危険: 元の名前そのまま (パストラバーサル攻撃)
$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(),
]);
複数ファイルアップロード
// 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]);
}
ストレージドライバ (S3 / GCS / ローカル)
# 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()); // 署名付き
画像のリサイズ・最適化
// 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);
}
進捗表示 (フロント側)
// 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);
制限・セキュリティ
- 容量制限:
php.ini の upload_max_filesize / post_max_size
- nginx:
client_max_body_size 50M;
- MIME 検証:
mimes: / mimetypes: バリデーション
- 拡張子と内容の整合性: 偽装ファイル対策(mime_content_type 等)
- ファイル名の正規化: パストラバーサル対策
- 実行可能ファイル禁止: .php / .exe / .sh 等を排除
- 仮想スキャン: ClamAV 等
関連記事