Đâ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:

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

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 -mCode 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 migrateCode 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 filenameoriginal_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>
    @endifCode 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!

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.

Để lại một bình luận

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *


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.