Skip to content
Thuan Bui's Blog
Go back

File Upload trong Laravel - [Phần 4] Hiển thị và xoá các file đã upload

Trong 3 phần đầu của series File Upload trong Laravel, mình đã chia sẻ cách xây dựng hệ thống upload file đơn giản trong Laravel:

Tuy nhiên, hệ thống hiện tại vẫn chưa có cơ chế quản lý các file đã upload:

Bài viết [Phần 4] hôm nay sẽ tiếp tục hoàn thiện hệ thống file upload với các tính năng mới:

I. Tạo model Upload và migration

Để có thể lưu thông tin của các file được upload vào database, mình sẽ tạo thêm Model Upload kèm theo migration file để tạo table tương ứng trong database.

Terminal window
php artisan make:model Upload -m

Chỉnh sửa lại file migration như bên dưới để tạo thêm 2 cột lưu tên file gốc (original_filename) và tên file được lưu (filename)

Schema::create('uploads', function (Blueprint $table) {
$table->id();
$table->string('filename');
$table->string('original_filename');
$table->timestamps();
});

Tạo table mới cho model Upload

Terminal window
php artisan migrate

Mở file app/Models/Upload.php và thêm phần khai báo $fillable để cho phép lưu thông tin vào 2 cột filenameoriginal_filename

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Upload extends Model
{
protected $fillable = [
'filename',
'original_filename'
];
}

II. Cập nhật Controller

Cập nhật lại UploadController, bổ sung dòng 44-48 để lưu thông tin của file được upload vào database

Terminal window
public function store(Request $request)
{
// Kiểm tra request chứa file không? dáp ứng các yêu cầu không
$request->validate([
'files' => 'required|array', // Tên input 'files[]' trong HTML
'files.*' => 'required|image|mimes:jpg,jpeg,png|max:2048', // max = 2MB mỗi file
]);
// Tạo biến mới để lưu đường dẫn tên file gốc
$storedFilePaths = []; // Array lưu đường dẫn các file đã lưu thành công
$originalFilenames = []; // Array lưu tên gốc của các file
$uploadedFiles = $request->file('files'); // Lấy array các đối tượng file đã upload
$numberOfFiles = count($uploadedFiles); // Đếm số lượng file đã upload
// Lặp qua từng file trong array $uploadedFiles
foreach ($uploadedFiles as $file) {
// Lấy tên file gốc từ client
$originalFilename = $file->getClientOriginalName();
$originalFilenames[] = $originalFilename; // Thêm tên gốc vào array
// Chuẩn bị các phần của tên file
$filenameWithoutExtension = pathinfo($originalFilename, PATHINFO_FILENAME); // Lấy tên file không phần mở rộng
$extension = $file->getClientOriginalExtension(); // Lấy phần mở rộng
$directory = 'uploads'; // Thư mục lưu file trên disk
$disk = 'public'; // Disk public sẽ sử dụng (được định nghĩa trong config/filesystems.php)
// Xác định tên file duy nhất
$finalFilename = $originalFilename; // Bắt đầu với tên gốc
$counter = 1;
// Kiểm tra xem file đã tồn tại chưa
while (Storage::disk($disk)->exists($directory . '/' . $finalFilename)) {
// Nếu tồn tại, tạo tên mới với hậu tố 1,2,3,...
$finalFilename = $filenameWithoutExtension . '-' . $counter . '.' . $extension;
$counter++;
}
// Lưu file bằng storeAs với tên file mới
$storedFilePath = $file->storeAs($directory, $finalFilename, $disk); // Trả về đường dẫn tương đối: 'uploads/ten_file_cuoi_cung.jpg'
$storedFilePaths[] = $storedFilePath; // Thêm đường dẫn file đã lưu vào array $storedFilePaths
// Tạo bản ghi mới trong table uploads của database
Upload::create([
'filename' => $storedFilePath,
'original_filename' => $originalFilename,
]);
}
// Chuyển hướng về trang trước đó
return back()->with('success', 'You have successfully uploaded ' . $numberOfFiles . ' files')
// Gửi kèm array các đường dẫn file đã lưu vào session flash data với key 'stored_paths'
->with('stored_paths', $storedFilePaths)
// Gửi kèm array các tên file gốc vào session flash data với key 'original_filenames'
->with('original_filenames', $originalFilenames);
}

Kiểm tra lại bằng cách upload thử 2 file mới

Thông tin tương ứng cho từng file đã được lưu thành công trong table uploads.

III. Hiển thị các files đã upload

Cập nhật lại hàm index trong UploadController

public function index()
{
$uploads = Upload::latest()->get();
return view('upload', compact('uploads'));
}

Bổ sung đoạn code sau vào upload.blade.php để hiển thị danh sách các file đã upload

@if (count($uploads) > 0)
<div class="container mx-auto mt-10 p-10 bg-white rounded-lg shadow-md max-w-md">
@foreach ($uploads as $upload)
<li class="flex items-center justify-between mb-4">
<a class="flex items-center gap-4 py-2" href="{{ $upload->filename }}" target="_blank">
<img src="{{ $upload->filename }}" alt="{{ $upload->filename }}" width="50" height="50">
<span>{{ $upload->original_filename }}</span>
</a>
</li>
@endforeach
</div>
@endif

Form upload giờ đã hiện ra danh sách các file đã upload trước đó. Thông tin được lấy từ dữ liệu được lưu trong database.

IV. Xóa file đã upload

Bổ sung thêm route vào file web.php

Route::delete('/upload/{upload}', [UploadController::class, 'destroy'])->name('upload.destroy');

Thêm hàm destroy trong UploadController

public function destroy(Upload $upload)
{
// Xoá file vật lý khỏi disk 'public' dựa vào đường dẫn lưu trong $upload->filename
Storage::disk('public')->delete($upload->filename);
// Xoá bản ghi tương ứng trong database
$upload->delete();
// Chuyển hướng người dùng về trang trước đó với thông báo thành công
return back()->with('success', 'You have successfully deleted ' . $upload->original_filename);
}

Chỉnh sửa lại phần hiển thị file đã upload trong upload.blade.php, bổ sung thêm nút Delete

@if (count($uploads) > 0)
<div class="container mx-auto mt-10 p-10 bg-white rounded-lg shadow-md max-w-md">
@foreach ($uploads as $upload)
<li class="flex items-center justify-between mb-4">
<a class="flex items-center gap-4 py-2" href="{{ $upload->filename }}" target="_blank">
<img src="{{ $upload->filename }}" alt="{{ $upload->filename }}" width="50" height="50">
<span>{{ $upload->original_filename }}</span>
</a>
<form action="{{ route("upload.destroy", $upload->id) }}"
method="POST"
style="display:inline;"
onsubmit="return confirm('Are you sure you want to delete this file?');">
@csrf
@method("DELETE")
<button type="submit" class="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded">Xóa</button>
</form>
</li>
@endforeach
</div>
@endif

Kiểm tra thành quả: mọi thứ hoạt động đúng như mong đợi: khi bấm nút Xóa, file sẽ bị xóa khỏi ổ cứng, đồng thời bản ghi tương ứng cũng bị xóa khỏi DB.

V. Lời kết

Trong [Phần 4] này, chúng ta đã nâng cấp hệ thống upload file với các tính năng mới:

Khi thông tin của file được lưu vào database, mình có thể dễ dàng phát triển thêm các tính năng mới: liên kết file với người dùng đã upload, hoặc liên kết vào bài viết, hoặc xây dựng một trang quản trị file riêng biệt,..

🔗 Mã nguồn

Tham khảo mã nguồn sử dụng trong [Phần 4] ở đây: https://github.com/10h30/laravel-file-upload-series/tree/part-4-manage-uploads

🔜 Phần 5: Upload lên Amazon S3

Hiện tại, toàn bộ file đang được lưu trên server nội bộ (local storage). Tuy nhiên, trong thực tế, việc lưu trữ file thường được chuyển sang các dịch vụ như Amazon S3 để:

Trong Phần 5, mình sẽ chia sẻ cách upload file lên Amazon S3, cấu hình disk cloud trong Laravel, và hiển thị link truy cập file từ S3.

Hẹn gặp lại ở [Phần 5] sẽ được ra lò vào tối Thứ Ba - 13/05/2025!


Share this post on:

Previous Post
Hướng dẫn tạo S3 Bucket và lấy Access Key trên AWS
Next Post
Các bước cần làm sau khi clone dự án laravel từ github về máy