Refactoring Laravel: Từ Magic String đến PHP Enum

· 4 min read

Trong nhiều năm, các lập trình viên Laravel quản lý state bằng "magic string" hoặc constant được định nghĩa ở đầu class Model.

// Cách cũ
class Post extends Model
{
    const STATUS_DRAFT = 'draft';
    const STATUS_PUBLISHED = 'published';
    const STATUS_ARCHIVED = 'archived';
}

Cách này hoạt động, nhưng thiếu type safety. Bạn có thể vô tình truyền 'publisshed' (lỗi chính tả) cho một hàm đang mong đợi status, và PHP sẽ không báo lỗi cho đến khi chạy.

Với PHP 8.1 và Laravel 9+, Enum là một bước ngoặt.

1. Định Nghĩa Enum

Tạo một class riêng cho các state của bạn.

namespace App\Enums;

enum PostStatus: string
{
    case Draft = 'draft';
    case Published = 'published';
    case Archived = 'archived';

    public function label(): string
    {
        return match($this) {
            self::Draft => 'Draft (Private)',
            self::Published => 'Live',
            self::Archived => 'Legacy',
        };
    }
    
    public function color(): string
    {
        return match($this) {
            self::Draft => 'gray',
            self::Published => 'green',
            self::Archived => 'red',
        };
    }
}

Lưu ý cách chúng ta có thể nhóm logic (label, màu cho UI) trực tiếp với định nghĩa dữ liệu.

2. Cast trong Model

Laravel giúp việc sử dụng Enum trở nên dễ dàng thông qua attribute casting.

use App\Enums\PostStatus;

class Post extends Model
{
    protected $casts = [
        'status' => PostStatus::class,
    ];
}

Bây giờ, khi bạn truy cập $post->status, bạn nhận được một instance của Enum, không phải string.

$post = Post::find(1);

if ($post->status === PostStatus::Published) {
    // so sánh nghiêm ngặt hoạt động!
}

// Truy cập helper method trực tiếp trong Blade
// <span class="bg-{{ $post->status->color() }}-500">

3. Validation Rule

Đã qua rồi những ngày dùng in:draft,published,archived. Sử dụng helper Rule::enum.

use Illuminate\Validation\Rule;

$request->validate([
    'status' => ['required', Rule::enum(PostStatus::class)],
]);

Điều này tự động đảm bảo input khớp với một trong các backing value hợp lệ (draft, published, v.v.).

4. Route Model Binding

Bạn thậm chí có thể bind Enum trong route!

Route::get('/posts/status/{status}', function (PostStatus $status) {
    return Post::where('status', $status)->get();
});

Nếu người dùng truy cập /posts/status/invalid-value, Laravel tự động trả về 404.

5. Type Hinting Mọi Nơi

Lợi ích lớn nhất là static analysis và hỗ trợ IDE.

function updateStatus(Post $post, PostStatus $newStatus): void
{
    $post->status = $newStatus;
    $post->save();
}

Nếu bạn cố gọi updateStatus($post, 'archived'), IDE hoặc PHPStan của bạn sẽ báo lỗi ngay lập tức. Điều này ngăn chặn cả một danh mục bug liên quan đến state không hợp lệ.

Kết Luận

Refactoring sang Enum tốn rất ít thời gian nhưng cải thiện đáng kể độ trưởng thành của codebase.

  1. Logic Tập Trung: Không còn phân tán logic label trong các file Blade.
  2. Type Safety: Bắt lỗi tại compile time.
  3. Dễ Đọc: PostStatus::Published là tự giải thích.

Hãy bắt đầu thay thế các định nghĩa const của bạn ngay hôm nay.

Bình luận