Phân tích lớp PrioritizedListenerRegistry trong League/Event qua mã nguồn

Trong kiến trúc ứng dụng PHP hiện đại, mô hình điều khiển bởi sự kiện (event-driven) đóng vai trò then chốt để xây dựng hệ thống linh hoạt và dễ mở rộng. Thành phần League/Event cung cấp một giải pháp nhẹ nhưng mạnh mẽ — lớp PrioritizedListenerRegistry — cho phép sắp xếp và thực thi các listener theo mức độ ưu tiên một cách chính xác và hiệu quả. Bài viết này đi sâu vào mã nguồn để làm rõ cơ chế hoạt động bên trong.

Cơ sở giao diện và thiết kế tổng thể

Lớp PrioritizedListenerRegistry được định nghĩa trong tệp src/PrioritizedListenerRegistry.php, đồng thời triển khai hai giao diện quan trọng:

  • League\Event\ListenerRegistry: giao diện tùy chỉnh của thư viện, hỗ trợ quản lý listener theo tên sự kiện.
  • Psr\EventDispatcher\ListenerProviderInterface: tuân thủ chuẩn PSR-14, đảm bảo khả năng tương thích với các framework và công cụ khác tuân thủ PSR.

Cấu trúc lưu trữ: Bản đồ sự kiện – listener

Bên trong lớp, dữ liệu được tổ chức dưới dạng mảng liên kết:

/** @var array<string, PrioritizedListenersForEvent> */
private array $listenersByEvent = [];

Mỗi khóa là tên sự kiện (ví dụ: "user.created"), giá trị tương ứng là một đối tượng PrioritizedListenersForEvent — thành phần chịu trách nhiệm duy nhất về việc phân loại, sắp xếp và cung cấp listener theo thứ tự ưu tiên.

Quản lý ưu tiên: Lớp PrioritizedListenersForEvent

Tệp src/PrioritizedListenersForEvent.php là lõi xử lý ưu tiên. Nó sử dụng cấu trúc mảng hai chiều:

/** @var array<int, array<int, callable>> */
private array $groupedListeners = [];

Ở đây:

  • Khóa ngoài là mức ưu tiên (số nguyên, càng lớn càng được thực thi trước).
  • Giá trị là mảng chứa các hàm gọi (callable) cùng mức ưu tiên đó.

Khi thêm listener mới bằng phương thức attach(), hệ thống sẽ:

  • Đưa listener vào nhóm tương ứng dựa trên mức ưu tiên đã chỉ định.
  • Đánh dấu bộ nhớ đệm sắp xếp là không còn hiệu lực ($this->sorted = false).

Thuật toán lấy danh sách listener

Phương thức getListeners(): array đảm nhiệm việc trả về dãy listener đã được sắp xếp đúng thứ tự. Quy trình gồm:

  1. Gọi krsort($this->groupedListeners) để sắp xếp giảm dần theo mức ưu tiên.
  2. Duyệt từng nhóm ưu tiên và gộp tất cả listener vào một mảng phẳng.
  3. Sử dụng array_filter() để loại bỏ các listener chỉ chạy một lần (OneTimeListener) đã hoàn tất.
  4. Lưu kết quả vào thuộc tính $this->cachedListeners nếu chưa có sẵn.

Hằng số ưu tiên tiêu chuẩn

Tệp src/ListenerPriority.php định nghĩa ba mức ưu tiên được khuyến nghị:

  • HIGH = 100: Dành cho logic nghiệp vụ bắt buộc (ví dụ: xác thực, kiểm tra ràng buộc).
  • NORMAL = 0: Mức mặc định, phù hợp với đa số hành vi trung gian.
  • LOW = -100: Dành cho tác vụ phụ trợ như ghi log, gửi thông báo không ảnh hưởng đến luồng chính.

Người dùng có thể truyền bất kỳ số nguyên nào vào tham số ưu tiên — giúp điều chỉnh chi tiết hơn khi cần.

Ví dụ minh họa

1. Đăng ký nhiều mức ưu tiên:

$registry = new PrioritizedListenerRegistry();

$registry->addListener('payment.completed', fn($e) => validatePayment($e), 200); // cao nhất
$registry->addListener('payment.completed', fn($e) => createInvoice($e), 50);   // trung bình
$registry->addListener('payment.completed', fn($e) => sendReceipt($e), -50);   // thấp

2. Listener chỉ chạy một lần:

$registry->addOnce('order.shipped', function($e) {
    applyLoyaltyPoints($e->getOrderId());
});

3. Hỗ trợ khớp sự kiện theo kiểu lớp hoặc tên:
Nếu sự kiện là một đối tượng kế thừa từ lớp cụ thể (ví dụ UserRegistered), hệ thống sẽ tìm listener đăng ký cho tên lớp đó. Ngoài ra, nếu sự kiện triển khai giao diện HasEventName, nó cũng có thể khớp theo tên trả về từ phương thức getEventName().

Tối ưu hiệu năng

  • Sắp xếp theo nhu cầu: Chỉ thực hiện khi có thay đổi về listener, tránh tính toán dư thừa.
  • Bộ nhớ đệm kết quả: Kết quả từ getListeners() được lưu lại cho đến khi có thay đổi.
  • Lọc nhanh: Sử dụng array_filter với callback hiệu quả để loại bỏ listener đã hết hạn.

Kết luận

PrioritizedListenerRegistry không chỉ đơn thuần là một bộ quản lý listener — mà là một cơ chế kiểm soát luồng sự kiện có chủ đích. Nhờ thiết kế rõ ràng, tuân thủ chuẩn và tối ưu hiệu năng, nó trở thành lựa chọn đáng tin cậy cho cả hệ thống nhỏ lẫn kiến trúc sự kiện phức tạp trong các ứng dụng PHP hiện đại.

Thẻ: league-event psr-14 php-events priority-queue listener-registry

Đăng vào ngày 3 tháng 7 lúc 01:39