Đây là bài viết Phần 4 nằm trong series: Laravel 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:
- Phần 1: Tạo form upload file, xử lý lưu file và cấu hình để truy cập qua URL.
- Phần 2: Thêm validation để đảm bảo file đúng định dạng, đúng kích thước yêu cầu.
- Phần 3: Nâng cấp tính năng upload nhiều file cùng lúc
Tuy nhiên, hệ thống hiện tại vẫn chưa có cơ chế quản lý các file đã upload:
- Không có cách nào xem danh sách file đã upload
- Không thể xoá file cũ khi không cần nữa
- Thiếu sự liên kết giữa file đã upload và các dữ liệu khác trong database
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:
- Lưu thông tin file đã upload vào database
- Hiển thị danh sách file đã upload
- Tạo nút xóa để có thể xoá file khỏi storage và xoá bản ghi trong DB
Mục Lục
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.
php artisan make:model Upload -m
Code language: CSS (css)
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();
});
Code language: PHP (php)
Tạo table mới cho model Upload
php artisan migrate
Code language: Nginx (nginx)
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 filename
và original_filename
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Upload extends Model
{
protected $fillable = [
'filename',
'original_filename'
];
}
Code language: HTML, XML (xml)
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
public function store(Request $request)
{
// Kiểm tra request có chứa file không? và có dáp ứng các yêu cầu không
$request->validate([
'files' => 'required|array', // Tên input là '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 và 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 có 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);
}
Code language: PHP (php)
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'));
}
Code language: PHP (php)
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
Code language: HTML, XML (xml)
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');
Code language: PHP (php)
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);
}
Code language: PHP (php)
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
Code language: HTML, XML (xml)
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:
- Tạo table
uploads
để lưu thông tin file vào database - Hiển thị danh sách file đã upload trên giao diện
- Thêm chức năng xoá file khỏi storage và database
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 để:
- Giảm tải cho server chính
- Tăng tốc độ truy cập qua CDN
- Bảo mật và ổn định hơn
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!
Bạn đang xem loạt bài viết: Laravel File Upload
- Phần 1: File Upload trong Laravel – [Phần 1] Tạo form, xử lý file, lưu trữ file
- Phần 2: File Upload trong Laravel – [Phần 2] Kiểm tra và bảo vệ file upload form
- Phần 3: File Upload trong Laravel – [Phần 3] Upload cùng lúc nhiều file
- Phần 4: File Upload trong Laravel – [Phần 4] Hiển thị và xoá các file đã upload
- Phần 5: File Upload trong Laravel – [Phần 5] Upload file lên Amazon S3
Nếu bạn cần hỗ trợ kỹ thuật miễn phí, vui lòng gửi câu hỏi trực tiếp ở phần Thảo luận bên dưới, mình sẽ trả lời trong thời gian sớm nhất.
Bạn cần hỗ trợ kỹ thuật chuyên sâu?
Khám phá các gói dịch vụ giúp bạn tối ưu công việc và vận hành hệ thống hiệu quả hơn. Từ chăm sóc website đến hỗ trợ kỹ thuật, mọi thứ đều linh hoạt và phù hợp với nhu cầu của bạn.