Đây là bài viết Phần 8 nằm trong series: Laravel File Upload
Ở các phần trước của series, chúng ta đã lần lượt đi qua các bước sau
- Phần 1 – Upload file cơ bản với Laravel
- Phần 2 – Áp dụng validation để kiểm tra định dạng, kích thước và bảo vệ upload
- Phần 3 – Upload nhiều file một lúc
- Phần 4 – Lưu thông tin upload vào database, hiển thị và xoá file
- Phần 5 – Upload file lên Amazon S3
- Phần 6 – Tạo temporary URL và kết nối với dịch vụ S3 self-hosted như MinIO
- Phần 7 – Tạo ảnh thumbnail cho ảnh upload bằng Intervention Image
Từ đầu series đến giờ, chúng ta sử dụng phương pháp thử công để xử lý upload. Tuy nhiên, Laravel có một lựa chọn khác chuyên nghiệp hơn, mạnh mẽ hơn để lo việc upload file, đó là thư viện Spatie Media Library.
- Tự động quản lý file theo từng model
- Dễ dàng gắn nhiều media vào một bản ghi
- Tạo conversion ảnh (thumbnail, resized,…)
- Tương thích tốt với các cloud disk như S3
Trong phần này, chúng ta sẽ cùng khám phá cách sử dụng Spatie Media Library để quản lý việc upload file.
Mục Lục
I. Giới thiệu Spatie Media Library
Spatie Media Library là một thư viện nổi tiếng giúp:
- Hỗ trợ convert ảnh (crop, resize) bằng cách kết hợp với Image driver
- Quản lý media (ảnh, video, file) dưới dạng model relationships
- Tự động generate thumbnail với
- Dễ dàng hiển thị file, kiểm tra loại file.
- Tích hợp với local, S3, hoặc cloud khác
II. Cài đặt Spatie Media Library
1. Cài đặt thư viện bằng composer
composer require "spatie/laravel-medialibrary"
Code language: JavaScript (javascript)
2. Publish file migrate & config
php artisan vendor:publish --provider="Spatie\MediaLibrary\MediaLibraryServiceProvider" --tag="medialibrary-config"
php artisan vendor:publish --provider="Spatie\MediaLibrary\MediaLibraryServiceProvider" --tag="medialibrary-config"
Code language: Nginx (nginx)
Chạy migration để tạo table mới cho Spatie Media Library
php artisan migrate
Code language: Nginx (nginx)
Database sẽ có thêm 1 table với tên gọi media, lưu các thông tin về file được upload.

File cấu hình thông số của Spetia được lưu ở config/media-library.php
. Trong đó, phần disk mặc định để lưu trữ file được khai báo với dòng 'disk_name' => env('MEDIA_DISK', 'public'),
<?php
return [
/*
* The disk on which to store added files and derived images by default. Choose
* one or more of the disks you've configured in config/filesystems.php.
*/
'disk_name' => env('MEDIA_DISK', 'public'),
Code language: HTML, XML (xml)
Mình muốn lưu trữ file vào MinIO nên cần bổ sung thông số vào file .env
MEDIA_DISK=minio
III. Cập nhật table Uploads
Table uploads
sẽ không còn cần dùng đến 2 cột filename
và thumbnail
nữa, do các đường dẫn liên quan sẽ được Spetia Media Library lưu trữ trong table media
.
Tạo file migration mới để xóa 2 cột filename
và thumbnail
khỏi table uploads
php artisan make:migration remove_filename_and_thumbnail_from_uploads_table --table=uploads
Code language: Nginx (nginx)
Chỉnh sửa file migration lại như sau
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('uploads', function (Blueprint $table) {
// Check if the columns exist before trying to drop them (optional, but good practice)
if (Schema::hasColumn('uploads', 'filename')) {
$table->dropColumn('filename');
}
if (Schema::hasColumn('uploads', 'thumbnail')) {
$table->dropColumn('thumbnail');
}
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('uploads', function (Blueprint $table) {
// Re-add the columns if rolling back.
// Adjust the type if they were different (e.g., text)
// Making them nullable as they might not have data if rolled back after new entries.
$table->string('filename')->nullable();
$table->string('thumbnail')->nullable();
});
}
};
Code language: HTML, XML (xml)
Chạy migration để áp dụng thay dổi
php artisan migrate
Code language: Nginx (nginx)
Sau đó cập nhật lại $fillable
trong model Upload
protected $fillable = [
'original_filename',
];
Code language: PHP (php)
IV. Upload file với Spetia Media Library
1. Cập nhật Model Upload
Cập nhật lại model Upload
, để sử dụng các tính năng của Spatie Media Library
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Storage;
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;
class Upload extends Model implements HasMedia
{
use InteractsWithMedia;
Code language: HTML, XML (xml)
Bổ sung thêm hàm registerMediaConversions
để tạo thumbnail khi upload ảnh.
use Spatie\MediaLibrary\MediaCollections\Models\Media;
public function registerMediaConversions(?Media $media = null): void
{
$this->addMediaConversion('thumbnail')
->width(100)
->height(100)
->performOnCollections('images'); // images là tên collection lưu ảnh
}
Code language: PHP (php)
Đối với các file ảnh đã upload trước khi thiết lập tính năng thumbnail, cần phải hcayj lệnh sau để tạo ảnh thumbnail.
php artisan media-library:regenerate
Code language: CSS (css)
2. Cập nhật hàm store trong UploadController
Cần phải loại bỏ các thông số và khai báo liên quan đến Intervention Image vì các tính năng này sẽ được xử lý bởi Spetia. Phần khai báo namespace chỉ còn như bên dưới
<?php
namespace App\Http\Controllers;
use App\Models\Upload;
use Illuminate\Http\Request;
Code language: HTML, XML (xml)
Mình cũng loại luôn bỏ phần xử lý đặt tên file không trùng tên, do Spatie Media Library sẽ tự động lo liệu việc này.
Hàm store
giờ được rút gọn
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
$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
// 3. Tạo bản ghi trong database cho model Upload:
// Lưu ý: Chúng ta chỉ cần lưu 'original_filename'.
// Các thông tin về đường dẫn file gốc và thumbnail sẽ do media-library quản lý.
$uploadEntry = Upload::create([
'original_filename' => $originalFilename,
]);
// 4. Đây là phần quan trọng nhất - Thêm file vào Media Library:
$uploadEntry->addMedia($file) // Thêm file vào Media Library
->toMediaCollection('images'); // Thêm file vào collection 'images'
}
// 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 tên file gốc vào session flash data với key 'original_filenames'
->with('original_filenames', $originalFilenames);
}
Code language: PHP (php)
Media library cho phép bạn tổ chức các file thành các “collection” (bộ sưu tập). Ở đây, mình đặt tên collection là ‘images’.
Khi file được thêm vào collection này, media-library sẽ tự động:
- Lưu file gốc vào disk đã cấu hình (trong
config/media-library.php
, mặc định là ‘public’, hoặc bạn có thể chỉ định disk khác, ví dụ ‘minio’ nếu đã cấu hình). - Tạo tên file duy nhất để tránh bị ghi đè nếu upload file trùng tên.
- Tự động tạo các “media conversion” (phiên bản chuyển đổi) đã được định nghĩa trong model
Upload
Khi sử dụng Spetia Media Library, chúng ta không cần phải xử lý thủ công các công việc như:
- Tự tạo tên file duy nhất.
- Dùng Intervention Image để resize ảnh thủ công.
- Dùng
Storage::put()
để lưu file gốc và thumbnail. - Tự quản lý đường dẫn của file gốc và thumbnail trong database (media-library làm việc này qua table
media
của nó).
3. Cập nhật hàm destroy trong UploadController
Tương tự, hàm destroy giờ sẽ được rút gọn lại tối đa
public function destroy(Upload $upload)
{
// Spatie Media Library sẽ tự động xoá các file liên quan (file gốc và các file chuyển đổi)
// khỏi disk khi model bị xoá.
$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)
4. Kiểm tra thực tế
Mình upload thử 2 file để kiểm tra. File đã được upload thành công. Hiện tại phần hiển thị ảnh chưa hoạt động, chúng ta sẽ cập nhật trong phần kế tiếp

Kiểm tra database, table uploads
có thêm 2 mục mới

Thông tin về file được lưu trữ trong table media

Kiểm tra trên MinIO, mỗi file sẽ được lưu trong 1 thư mục riêng, tên thư mục tương ứng với id của file trong table media

Tuy nhiên, hiện tại chỉ mới có file gốc, chưa thấy file thumbnail đâu cả.

5. Xử lý queue
Mặc địch, các thao tác xử lý ảnh sẽ được xếp vào queue (hàng đợi). Chúng ta có thể kiểm tra trong table jobs
, sẽ thấy 2 jobs đang đợi

Để hệ thống xử lý queue, cần phải chạy lệnh
php artisan queue:work
Code language: CSS (css)
Sau khi hoàn thành, chúng ta sẽ thấy có thêm thư mục conversions, chứa các file thumbnail được tạo ra.

Nếu muốn hệ thống xử lý ngay lập tức, không bị đẩy vào queue, cần cập nhật lại hàm registerMediaConversions
, bổ sung thông số nonQueued
public function registerMediaConversions(?Media $media = null): void
{
$this->addMediaConversion('thumbnail')
->width(100)
->height(100)
->nonQueued()
->performOnCollections('images'); // images là tên collection lưu ảnh
}
Code language: PHP (php)
Hoặc có thể chỉnh cho tất cả thao tác xử lý đều xử lý trực tiếp bằng cách bổ sung thông số sau vào file .env
queue_conversions_by_default=false
Code language: JavaScript (javascript)
V. Hiển thị file sau khi Upload
Để hệ thống hiển thị file sau khi Upload, mình cần phải chỉnh sửa lại hai hàm getUrlAttribute
và getThumbnailUrlAttribute
trong model Upload
1. Chỉnh sửa hàm getUrlAttribute
public function getUrlAttribute(): string
{
$mediaItem = $this->getFirstMedia('images');
if ($mediaItem) {
// Tạo URL tạm thời cho file gốc.
return $mediaItem->getTemporaryUrl(now()->addMinutes(5));
}
return ''; // Trả về chuỗi rỗng hoặc một URL placeholder nếu không tìm thấy file
}
Code language: PHP (php)
2. Chỉnh sửa hàm getThumbnailUrlAttribute
public function getThumbnailUrlAttribute(): string
{
$mediaItem = $this->getFirstMedia('images');
if ($mediaItem) {
// Tạo URL tạm thời cho file thumbnail (conversion)
return $mediaItem->getTemporaryUrl(now()->addMinutes(5), 'thumbnail');
}
return ''; // Trả về chuỗi rỗng hoặc một URL placeholder nếu không tìm thấy thumbnail
}
Code language: PHP (php)
3. Kiểm tra thành quả
Các file sau khi upload giờ đã được hiển thị ngon lành trên app. Khi bấm Delete, bản ghi trong table uploads
và media
của Database sẽ bị xóa. Đồng thời các file liên quan trên S3 cũng sẽ được xóa tương ứng.

VI. Lời kết
Trong [Phần 8] này, chúng ta đã tìm hiểu cách sử dụng Spatie Media Library để xử lý việc upload file một cách linh hoạt và mạnh mẽ hơn:
- Kết nối file upload trực tiếp với model (ví dụ:
Upload
,Post
) - Tự động tạo thư mục, quản lý tên file (tránh trùng lặp)
- Dễ dàng lấy đường dẫn ảnh, tạo thumbnail
Việc áp dụng Media Library không chỉ giúp code gọn gàng hơn, mà còn tăng khả năng mở rộng và kiểm soát file rõ ràng hơn. Bạn cũng có thể áp dụng các tính năng như chuyển file sang cloud, tạo conversion nâng cao, hoặc cấu hình đường dẫn tùy theo nhu cầu sử dụng.
🔗 Mã nguồn
Tham khảo mã nguồn sử dụng trong [Phần 8] ở đây: https://github.com/10h30/laravel-file-upload-series/tree/part-8-spatie-media-library
🔜 Phần 9: Nâng cấp giao diện upload file với FilePond
Chúng ta sẽ nâng cấp giao diện upload trở nên thân thiện hơn với người dùng, bằng cách sử dụng FilePond – một thư viện drag-n-drop hiện đại và đẹp mắt.
Hẹn gặp lại bạn trong [Phần 9], sẽ được ra lò vào ngày Thứ Tư 21/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
- Phần 6: File Upload trong Laravel – [Phần 6] Temporary URL và Upload lên MinIO (self-hosted S3)
- Phần 7: File Upload trong Laravel – [Phần 7] Tạo thumbnail (ảnh thu nhỏ) với Intervention Image
- Phần 8: File Upload trong Laravel – [Phần 8] Quản lý ảnh nâng cao với Spatie Media Library
- Phần 9: File Upload trong Laravel – [Phần 9] Nâng cấp giao diện upload file với FilePond