Skip to content
Thuan Bui's Blog
Go back

File Upload trong Laravel - [Phần 8] Quản lý ảnh nâng cao với Spatie Media Library

Ở các phần trước của series, chúng ta đã lần lượt đi qua các bước sau

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.

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.

I. Giới thiệu Spatie Media Library

Spatie Media Library là một thư viện nổi tiếng giúp:

II. Cài đặt Spatie Media Library

1. Cài đặt thư viện bằng composer

Terminal window
composer require "spatie/laravel-medialibrary"

2. Publish file migrate & config

Terminal window
php artisan vendor:publish --provider="Spatie\MediaLibrary\MediaLibraryServiceProvider" --tag="medialibrary-config"
php artisan vendor:publish --provider="Spatie\MediaLibrary\MediaLibraryServiceProvider" --tag="medialibrary-config"

Chạy migration để tạo table mới cho Spatie Media Library

Terminal window
php artisan migrate

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'),

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 filenamethumbnail 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 filenamethumbnail khỏi table uploads

Terminal window
php artisan make:migration remove_filename_and_thumbnail_from_uploads_table --table=uploads

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();
});
}
};

Chạy migration để áp dụng thay dổi

Terminal window
php artisan migrate

Sau đó cập nhật lại $fillable trong model Upload

protected $fillable = [
'original_filename',
];

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;

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
}

Đố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.

Terminal window
php artisan media-library:regenerate

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;

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

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
$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 thumbnail sẽ do media-library quản lý.
$uploadEntry = Upload::create([
'original_filename' => $originalFilename,
]);
// 4. Đây 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);
}

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:

  1. 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).

  2. Tạo tên file duy nhất để tránh bị ghi đè nếu upload file trùng tên.

  3. 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ư:

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);
}

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

Terminal window
php artisan queue:work

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
}

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

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 getUrlAttributegetThumbnailUrlAttribute 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
}

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
}

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 uploadsmedia 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:

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.


Share this post on:

Previous Post
File Upload trong Laravel - [Phần 9] Nâng cấp giao diện upload file với FilePond
Next Post
File Upload trong Laravel - [Phần 7] Tạo thumbnail (ảnh thu nhỏ) với Intervention Image