Mục đích sử dụng EventManager
zend-eventmanager
là một thành phần của ZF, được thiết kế với các mục đích:
- Đơn giản hóa triển khai code theo mô hình OP (Observer Pattern).
- Triển khai thiết kế phần mềm theo khía cạnh AOP (Aspect-Oriented Programming).
- Triên khai các kiến trúc dựa trên sự kiện
Kiến trúc cơ bản của EventManager là cho phép bạn đính vào hay gỡ ra các Listener cho một loại sự kiện nào đó, kích hoạt sự kiện, ngắt thi hành các listener.
Để cài đặt EventManager của ZF dùng lệnh Composer sau dưa vào dự án. Hoặc nếu sử dụng ZF skeleton thì đã có sẵn.
composer require zendframework/zend-eventmanager
Hoặc tự tải về từ GitHub và cài đặt thủ công Zend Event Manager. Ở đây tao thư mục eventmanageraxample
và gõ lệnh composer trên trong nó đề cài đặt.
Khám phá Event Manager
Nhắc lại một số khái niệm
- Một sự kiện (Event) đơn giản là tên của một hành động nào đó: Sự kiện A, sự kiện B, sự kiện UploadPhoto ...
- Listener (chờ sự kiện) là một hàm, lớp PHP Callback mà nó sẽ được triệu gọi để trả lời một sự kiện.
- Một EventManager thì là tập hợp các Listener để đáp ứng một số sự kiện và kích hoạt sự kiện.
Thường thì một sự kiện sẽ được mô hình hóa như một đối tượng, sự kiện phải chứa trong nó các thông tin mô tả và làm sao để kích hoạt sự kiện (tức chứa tên sự kiện và đối tượng cần gọi để đáp ứng lại sự kiện - đối tượng này là mục tiêu (target
), và các tham số khác).
Triển khai code Event Manager đầu tiên
Trở lại thư mục eventmanageraxample
trên tạo file index.php và cập nhật code ứng dụng.
Tạo một Listener
có tên là tuvanAZ
, nó lắng nghe sự kiện gọi đến, giả sử đặt tên sự kiện là xintuvan
. Sau đó tạo đối tượng EventManager, rồi đăng ký tuvanAZ vào với ấn định là tuvanAZ sẽ xử lý sự kiện có tên là xintuvan
.
Như vậy ở bất kỳ đâu, có thể gửi Event
tên xintuvan
với tham số truyền tải là $params bằng hàm trigger
.
<?php use Zend\EventManager\EventManager; include "vendor/autoload.php"; /** * @param Zend\EventManager\Event $event */ function tuvanAZ($event) { $params = $event->getParams(); $rt = "Tư vấn: $params[yeucau] => Câu hỏi khó thế!<br>"; print $rt; return $rt; } $events = new EventManager(); $events->attach('xintuvan', 'tuvanAZ'); $params = ['ten' => 'Nguyen Van A', 'yeucau' => 'Giải đáp thắc mắc về XYZ :)']; $rt = $events->trigger('xintuvan', null, $params);
Chạy mã trên thì in ra dòng thông báo: Tư vấn: Giải đáp thắc mắc về XYZ :) => Câu hỏi khó thế!
Bạn đã tạo ra một mô hình lập trình theo sự kiện đơn giản với Zend EventManager rồi.
Để tạo Listener ngoài cách trên còn có nhiều cách khác bạn sẽ tìm hiểu sau, ví dụ có thể dùng closures
như sau cũng có kết quả tương tự:
<?php use Zend\EventManager\EventManager; include "vendor/autoload.php"; $events = new EventManager(); $events->attach('xintuvan', function($event) { $params = $event->getParams(); $rt = "Tư vấn: $params[yeucau] => Câu hỏi khó thế!<br>"; print $rt; return $rt; }); $params = ['ten' => 'Nguyen Van A', 'yeucau' => 'Giải đáp thắc mắc về XYZ :)']; $va = $events->trigger('xintuvan', null, $params);
Trong ví dụ trên, tham số thứ 2 của trigger là null, đây là tham số có về target và ví dụ không cần biết target nên không dùng. Tuy nhiên thực tế thì bạn thường gọi trigger bên trong một lớp nào đó, lúc này bạn nên sử dụng tham số này để chỉ lớp mà event được gửi đi và listener sẽ biết đó là đối tượng nào gửi thông điệp bằng cách lấy tham số từ $event->getTarget()
. Xem ví dụ dưới đây.
Gửi Event trong một lớp và truyền tham số target cho trigger
<?php use Zend\EventManager\EventManager; include "vendor/autoload.php"; class Abc { /** * @var EventManager */ protected $eventmanager; public function setEventManager($em) { $this->eventmanager = $em; } public function sendEvent() { $params = ['ten' => 'Nguyen Van A', 'yeucau' => 'Giải đáp thắc mắc về XYZ :)']; $this->eventmanager->trigger('xintuvan', $this, $params); } } $events = new EventManager(); $events->attach('xintuvan', function($event) { $params = $event->getParams(); $target = $event->getTarget(); $rt = "; Tư vấn: $params[yeucau] => Câu hỏi khó thế!<br>"; print 'Target is:'.get_class($target).$rt; return $rt; }); $abc = new Abc(); $abc->setEventManager($events); $abc->sendEvent();
Trên đây là cách triển khai code EventManager thông thường. Ngoài ra còn có cách triển khai theo ShareEventManager với một số lưu ý.
Sử dụng SharedEventManager
Ý tưởng là để quản lý nhiều EventManager khác nhau lúc này sẽ sử dụng thêm SharedEventManager. Đâu tiên cần tạo ra một đối tượng SharedEventManager bằng cách thức như sau:
$shareEventsManager = new SharedEventManager();
Sau khi đó các đối tượng EventManager thông thường khi khởi tạo có thêm thao số chỉ ra SharedEventManager và mỗi EventManager thường phải có các định danh xác định bằng hàm setIdentifiers
.
Ví dụ tạo một ShareManager có sử dụng đến SharedManager ở trên.
$shareEventsManager = new SharedEventManager();
$eventManager1 = new EventManager($shareEventsManager);
$eventManager1->setIdentifiers(['DINH_DANH_1', 'DINH_DANH_2']);
Lúc này việc attach
các Listener vào EventManager nên thông qua attach
của SharedEventManager với cách là chỉ ra thêm thao số $identifier nhằm chỉ ra loại EventManager nào được gắn Listener.
public function attach($identifier, $event, callable $listener, $priority = 1)
Quay trở lại vị dụ trên, sửa code để dử dụng SharedEventManager với hai EventManager.
<?php use Zend\EventManager\SharedEventManager; use Zend\EventManager\EventManager; include "vendor/autoload.php"; class Abc { /** * @var EventManager */ protected $eventmanager; public function setEventManager($em) { $this->eventmanager = $em; } public function sendEvent() { $params = ['ten' => 'Nguyen Van A', 'yeucau' => 'Giải đáp thắc mắc về XYZ :)']; $this->eventmanager->trigger('xintuvan', $this, $params); } } $shareEventsManager = new SharedEventManager(); $eventManager1 = new EventManager($shareEventsManager); $eventManager2 = new EventManager($shareEventsManager); $eventManager1->setIdentifiers(['EVENTMANAGER1']); $eventManager2->setIdentifiers(['EVENTMANAGER2']); $shareEventsManager->attach('EVENTMANAGER1', 'xintuvan', function($event) { $params = $event->getParams(); $target = $event->getTarget(); $rt = "; Tư vấn: $params[yeucau] => Câu hỏi khó thế!<br>"; print 'Target is:'.get_class($target).$rt; return $rt; }); $abc = new Abc(); $abc->setEventManager($eventManager1); $abc->sendEvent();
Trong ví dụ trên, khi attact đã chỉ ra rõ định danh của EventManager só EVENTMANAGER1, là EventManager mà đối Abc sử dụng để gửi sự kiện. Nếu thay bằng EVENTMANAGER2 thì Listener sẽ gắn vào $eventManager2, và lúc đó Abc gửi sự kiện sẽ không tới được Listener và Listener không thuộc $eventManager1.
Sử dụng ký tự đại diện Wildcards
Bạn có thể sử dụng ký tự *
để chỉ ra nhiều định danh, nhiều tên event.
- Gắn Listener lắng nghe bất kể sự kiện có tên như thế nào:
$eventsmanager->attach('*', $listener);
- Gắn Listener cho eventmanager có định danh foo, lắng nghe tất cả tên sự kiện gửi đi:
$sharedEvents->attach('foo', '*', $listener);
- Gắn Listener lắng nghe sự kiện foo đến tất cả các EventManager:
$sharedEvents->attach('*', 'foo', $listener);
- Gắn Listener lắng nghe tất cả các sự kiện đến tất cả các EventManager:
$sharedEvents->attach('*', '*', $listener);
Cách tạo và sử dụng một tập hợp Listener (Callback)
Đây là cách sử dụng ListenerAggregateInterface
, nhắm gắn một lúc nhiều listener vào EventManager
. Sau khi triển khai mã theo lớp này thì bên trong nó có thể chứa nhiều Listener và đế chúng vào EventManager thì gọi phương thức sau của ListenerAggrgateInterface:
attach(EventManagerInterface $events, $priority = 1); detach(EventManagerInterface $events);
Ví dụ sau chỉ ra cách tạo và sử dụng ListenerAggregateInterface như thế nào:
<?php use Zend\EventManager\SharedEventManager; use Zend\EventManager\EventManager; use Zend\EventManager\ListenerAggregateInterface; include "vendor/autoload.php"; class MyListeners implements ListenerAggregateInterface { //Luu lai các Listener đã attact để sau này hàm deach sử dụng private $listeners = []; public function attach(Zend\EventManager\EventManagerInterface $events, $priority = 1) { $this->listeners[] = $events->attach('something', [$this, 'onSomething'], $priority); $this->listeners[] = $events->attach('else', [$this, 'onElse'], $priority); $this->listeners[] = $events->attach('xintuvan', [$this, 'onXintuvan'], $priority); } public function detach(Zend\EventManager\EventManagerInterface $events) { foreach ($this->listeners as $index => $listener) { $events->detach($listener); unset($this->listeners[$index]); } } public function onSomething(\Zend\EventManager\EventInterface $event) { } public function onElse(\Zend\EventManager\EventInterface $event) { echo "<h2>SOMETHING ELSE</h2>"; } public function onXintuvan(\Zend\EventManager\EventInterface $event) { $params = $event->getParams(); $target = $event->getTarget(); $rt = "; Tư vấn: $params[yeucau] => Câu hỏi khó thế!<br>"; print 'Target is:'.get_class($target).$rt; return $rt; } } class Abc { /** * @var EventManager */ protected $eventmanager; public function setEventManager($em) { $this->eventmanager = $em; } public function sendEvent() { $params = ['ten' => 'Nguyen Van A', 'yeucau' => 'Giải đáp thắc mắc về XYZ :)']; $this->eventmanager->trigger('xintuvan', $this, $params); $this->eventmanager->trigger('else', $this, $params); } } $eventManager1 = new EventManager(); $mylistener = new MyListeners(); $mylistener->attach($eventManager1); $abc = new Abc(); $abc->setEventManager($eventManager1); $abc->sendEvent();
Ở ví dụ trên đã tạo và đăng ký 3 sự kiện là something
, else
, xintuvan
trong một tập hợp sau đó gắn vào EventManager.
Kết quả trả về sau khi gửi Event từ trigger
Sau khi một event được gửi đi, các listener xử lý và trả về một ResponseCollection, đó là một tập hợp (Một event gửi đi có thể có nhiều Listener xử lý). ResponseCollection là đối lớp kế thừa từ SplStack nên bạn có thể dùng các phương thức của SplStack để lấy kết quả. Trong đó có các phương thức như first, last để lấy kết quả đầu tiên và cuối cùng trả về.
Độ ưu tiên
Khi một event mà được nhiều Listener xử lý, nếu bạn muốn ấn định Listener nào thi hành trước, Listener nào thi hành sau thì bạn cần thiết lập tham số $priority
ở hàm attach( ... )
. $priority là số interger trong đó giá trị cao thì có độ ưu tiên cao hơn (chạy trước).
Ngưng chuyển Event đến các Listener khác
Khi một event mà được nhiều Listener xử lý, nếu bạn muốn event không được chuyển đến các Listener khác thì làm như sau:
$events->attach('do', function ($e) { $e->stopPropagation(); return new SomeResultClass(); });
Lazy Listener
Trong các ví dụ trình bày ở trên thì các Listener được attact là các đối tượng cụ thể, nhiều khi đối tượng không được sử dụng dẫn tới ảnh hưởng hiệu xuất của ứng dụng. Từ ZF3 cung cấp thêm cách tải sau Listener, Listener chỉ thực sự được tạo ra khi nó được gọi đến. Để sử dụng Lazy Listener đảm bảo dự án đã được thêm thư viện container-interop.
composer require container-interop/container-interop
Tạo Lazy Listener - LazyListener
Bạn tạo LazyListener
bằng cách chuyển tới hàm khởi tạo của nó các tham số:
- Một mảng định nghĩa về listener gồm: tên của listener, tên của phương thức cần gọi khi tạo listener
- Một trình chứa ví dụ Service Manager của ZF.
- Mảng tham số thêm cần dùng để tạo Listener.
Nếu dùng Server Manager làm trình chứa thì cài thêm vào bằng lệnh:
composer require zendframework/zend-servicemanager
Đây là cách triển khai:
use My\Application\Listener; use Zend\EventManager\LazyListener; $events->attach('foo', new LazyListener([ 'listener' => Listener::class, 'method' => 'onDispatch', ], $container));
Ví dụ:
<?php use Zend\EventManager\SharedEventManager; use Zend\EventManager\EventManager; use Zend\EventManager\ListenerAggregateInterface; use Zend\EventManager\LazyListener; include "vendor/autoload.php"; class MyListener { public function __invoke($sm) { return $this; } public function tuvan($event) { $params = $event->getParams(); $target = $event->getTarget(); $rt = "; Tư vấn: $params[yeucau] => Câu hỏi khó thế!<br>"; print 'Target is:'.get_class($target).$rt; return $rt; } } class Abc { /** * @var EventManager */ protected $eventmanager; public function setEventManager($em) { $this->eventmanager = $em; } public function sendEvent() { $params = ['ten' => 'Nguyen Van A', 'yeucau' => 'Giải đáp thắc mắc về XYZ :)']; $this->eventmanager->trigger('xintuvan', $this, $params); } } $serviceManager = new \Zend\ServiceManager\ServiceManager([ 'factories' => [ \MyListener::class => \MyListener::class, ], ]); $events = new EventManager(); $events->attach('xintuvan', new LazyListener([ 'MyListener' => \MyListener::class, 'method' => 'tuvan', ], $serviceManager)); $abc = new Abc(); $abc->setEventManager($events); $abc->sendEvent();
LazyEventListener
Tạo tương tự LazyListener nhưng thêm cấc tham số event, priority.
use My\Application\Listener; use Zend\EventManager\LazyEventListener; $listener = new LazyEventListener([ 'listener' => Listener::class, 'method' => 'onDispatch', 'event' => 'dispatch', 'priority' => 100, ], $container);
LazyListenerAggregate
Dùng LazyListenerAggregate để tạo ra một tập hợp các LazyListener. Tạo ra bằng cách định nghĩa các LazyListerner trong một mảng.
use My\Application\DispatchListener; use My\Application\RouteListener; use Zend\EventManager\LazyListenerAggregate; $definitions = [ [ 'listener' => RouteListener::class, 'method' => 'onRoute', 'event' => 'route', 'priority' => 100, ], [ 'listener' => DispatchListener::class, 'method' => 'onDispatch', 'event' => 'dispatch', 'priority' => -100, ], ]; $aggregate = new LazyListenerAggregate( $definitions, $container ); $aggregate->attach($events);
Lưu ý nên sử dụng cách này.