├── .gitignore ├── LICENSE ├── README.md ├── README_en.md ├── composer.json ├── examples ├── App.php ├── AppListener.php ├── EnumGroupListener.php ├── ExamHandler.php ├── demo.php ├── enum-group.php ├── named-group.php └── test.php ├── phpunit.xml.dist ├── src ├── ClassEvent.php ├── Event.php ├── EventHandlerInterface.php ├── EventInterface.php ├── EventManager.php ├── EventManagerAwareTrait.php ├── EventManagerInterface.php ├── EventSubscriberInterface.php └── Listener │ ├── LazyListener.php │ ├── ListenerPriority.php │ └── ListenerQueue.php └── test ├── EventManagerTest.php ├── EventTest.php ├── LazyListenerTest.php ├── ListenerQueueTest.php └── boot.php /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .phpintel/ 3 | .vscode/ 4 | tmp/* 5 | !README.md 6 | composer.lock 7 | .local 8 | *.pid 9 | *.log 10 | .DS_Store 11 | .project 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 inhere 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Event Dispatcher 2 | 3 | [![License](https://img.shields.io/packagist/l/inhere/event.svg?style=flat-square)](LICENSE) 4 | [![Php Version](https://img.shields.io/badge/php-%3E=7.1.0-brightgreen.svg?maxAge=2592000)](https://packagist.org/packages/inhere/event) 5 | [![Latest Stable Version](http://img.shields.io/packagist/v/inhere/event.svg)](https://packagist.org/packages/inhere/event) 6 | 7 | > **[EN README](./README_en.md)** 8 | 9 | 简洁, 功能完善的事件管理调度实现 10 | 11 | - 实现自 [Psr 14](https://github.com/php-fig/fig-standards/blob/master/proposed/event-dispatcher.md) - 事件调度器 12 | - 支持对一个事件添加多个监听器 13 | - 支持设置事件优先级 14 | - 支持快速的事件组注册 15 | - 支持根据事件名称来快速的对事件组监听 16 | - eg 触发 `app.run`, `app.end` 都将同时会触发 `app.*` 事件 17 | - 支持通配符事件的监听 18 | 19 | ## 项目地址 20 | 21 | - **github** https://github.com/inhere/php-event-manager.git 22 | 23 | ## 安装 24 | 25 | - composer 命令 26 | 27 | ```php 28 | composer require inhere/event 29 | ``` 30 | 31 | - composer.json 32 | 33 | ```json 34 | { 35 | "require": { 36 | "inhere/event": "dev-master" 37 | } 38 | } 39 | ``` 40 | 41 | ### 事件调度器 42 | 43 | 事件调度器, 也可称之为事件管理器。事件的注册、监听器注册、调度(触发)都是由它管理的。 44 | 45 | ```php 46 | use Inhere\Event\EventManager; 47 | 48 | $em = new EventManager(); 49 | ``` 50 | 51 | ## 事件监听器 52 | 53 | 监听器允许是: 54 | 55 | 1. function 函数 56 | 2. 一个闭包 57 | 3. 一个监听器类(可以有多种方式) 58 | 59 | ### 1. function 60 | 61 | ```php 62 | // ... 63 | 64 | $em->attach(Mailer::EVENT_MESSAGE_SENT, 'my_function'); 65 | ``` 66 | 67 | ### 2. 闭包 68 | 69 | ```php 70 | // ... 71 | 72 | $em->attach(Mailer::EVENT_MESSAGE_SENT, function(Event $event) { 73 | // $message = $event->message; 74 | // ... some logic 75 | }); 76 | ``` 77 | 78 | ### 3. 监听器类(有多种方式) 79 | 80 | - 类里面存在跟事件相同名称的方法 81 | 82 | > 此种方式可以在类里面写多个事件的处理方法 83 | 84 | ```php 85 | class ExamListener1 86 | { 87 | public function messageSent(EventInterface $event) 88 | { 89 | echo "handle the event {$event->getName()}\n"; 90 | } 91 | } 92 | ``` 93 | 94 | - 一个类(含有 `__invoke` 方法) 95 | 96 | > 此时这个类对象就相当于一个闭包 97 | 98 | ```php 99 | class ExamListener2 100 | { 101 | public function __invoke(EventInterface $event) 102 | { 103 | echo "handle the event {$event->getName()}\n"; 104 | } 105 | } 106 | ``` 107 | 108 | - 实现接口 `EventHandlerInterface` 109 | 110 | 触发时会自动调用 `handle()` 方法。 111 | 112 | ```php 113 | class ExamHandler implements EventHandlerInterface 114 | { 115 | /** 116 | * @param EventInterface $event 117 | * @return mixed 118 | */ 119 | public function handle(EventInterface $event) 120 | { 121 | // TODO: Implement handle() method. 122 | } 123 | } 124 | ``` 125 | 126 | - 实现接口 `EventSubscriberInterface` 127 | 128 | 可以在一个类里面自定义监听多个事件 129 | 130 | ```php 131 | /** 132 | * Class EnumGroupListener 133 | * @package Inhere\Event\Examples 134 | */ 135 | class EnumGroupListener implements EventSubscriberInterface 136 | { 137 | const TEST_EVENT = 'test'; 138 | const POST_EVENT = 'post'; 139 | 140 | /** 141 | * 配置事件与对应的处理方法 142 | * @return array 143 | */ 144 | public static function getSubscribedEvents(): array 145 | { 146 | return [ 147 | self::TEST_EVENT => 'onTest', 148 | self::POST_EVENT => ['onPost', ListenerPriority::LOW], // 还可以配置优先级 149 | ]; 150 | } 151 | 152 | public function onTest(EventInterface $event) 153 | { 154 | $pos = __METHOD__; 155 | echo "handle the event {$event->getName()} on the: $pos\n"; 156 | } 157 | 158 | public function onPost(EventInterface $event) 159 | { 160 | $pos = __METHOD__; 161 | echo "handle the event {$event->getName()} on the: $pos\n"; 162 | } 163 | } 164 | ``` 165 | 166 | ## 快速使用 167 | 168 | ### 1. 绑定事件触发 169 | 170 | ```php 171 | // a pre-defined event 172 | class MessageEvent extends Event 173 | { 174 | // append property ... 175 | public $message; 176 | } 177 | 178 | // in the business 179 | class Mailer 180 | { 181 | use EventManagerAwareTrait; 182 | 183 | const EVENT_MESSAGE_SENT = 'messageSent'; 184 | 185 | public function send($message) 186 | { 187 | // ...发送 $message 的逻辑... 188 | 189 | $event = new MessageEvent(self::EVENT_MESSAGE_SENT); 190 | $event->message = $message; 191 | 192 | // 事件触发 193 | $this->eventManager->trigger($event); 194 | } 195 | } 196 | ``` 197 | 198 | ### 2. 触发事件 199 | 200 | ```php 201 | $em = new EventManager(); 202 | 203 | // 绑定事件 204 | $em->attach(Mailer::EVENT_MESSAGE_SENT, 'exam_handler'); 205 | $em->attach(Mailer::EVENT_MESSAGE_SENT, function (EventInterface $event) 206 | { 207 | $pos = __METHOD__; 208 | echo "handle the event {$event->getName()} on the: $pos\n"; 209 | }); 210 | 211 | // 这里给它设置了更高的优先级 212 | $em->attach(Mailer::EVENT_MESSAGE_SENT, new ExamListener1(), 10); 213 | $em->attach(Mailer::EVENT_MESSAGE_SENT, new ExamListener2()); 214 | $em->attach(Mailer::EVENT_MESSAGE_SENT, new ExamHandler()); 215 | 216 | $mailer = new Mailer(); 217 | $mailer->setEventManager($em); 218 | 219 | // 执行,将会触发事件 220 | $mailer->send('hello, world!'); 221 | ``` 222 | 223 | ### 3. 运行示例 224 | 225 | 完整的实例代码在 `examples/demo.php` 中。 226 | 227 | 运行: `php examples/demo.php` 228 | 229 | 输出: 230 | 231 | ```text 232 | $ php examples/exam.php 233 | handle the event 'messageSent' on the: ExamListener1::messageSent // 更高优先级的先调用 234 | handle the event 'messageSent' on the: exam_handler 235 | handle the event 'messageSent' on the: {closure} 236 | handle the event 'messageSent' on the: ExamListener2::__invoke 237 | handle the event 'messageSent' on the: Inhere\Event\Examples\ExamHandler::handle 238 | 239 | ``` 240 | 241 | ## 一组事件的监听器 242 | 243 | 除了一些特殊的事件外,在一个应用中,大多数事件是有关联的,此时我们就可以对事件进行分组,方便识别和管理使用。 244 | 245 | - **事件分组** 推荐将相关的事件,在名称设计上进行分组 246 | 247 | 例如: 248 | 249 | ```text 250 | // 模型相关: 251 | model.insert 252 | model.update 253 | model.delete 254 | 255 | // DB相关: 256 | db.connect 257 | db.disconnect 258 | db.query 259 | 260 | // 应用相关: 261 | app.start 262 | app.run 263 | app.stop 264 | ``` 265 | 266 | ### 1. 一个简单的示例应用类 267 | 268 | ```php 269 | 270 | /** 271 | * Class App 272 | * @package Inhere\Event\Examples 273 | */ 274 | class App 275 | { 276 | use EventManagerAwareTrait; 277 | 278 | const ON_START = 'app.start'; 279 | const ON_STOP = 'app.stop'; 280 | const ON_BEFORE_REQUEST = 'app.beforeRequest'; 281 | const ON_AFTER_REQUEST = 'app.afterRequest'; 282 | 283 | public function __construct(EventManager $em) 284 | { 285 | $this->setEventManager($em); 286 | 287 | $this->eventManager->trigger(new Event(self::ON_START, [ 288 | 'key' => 'val' 289 | ])); 290 | } 291 | 292 | public function run() 293 | { 294 | $sleep = 0; 295 | $this->eventManager->trigger(self::ON_BEFORE_REQUEST); 296 | 297 | echo 'request handling '; 298 | while ($sleep <= 3) { 299 | $sleep++; 300 | echo '.'; 301 | sleep(1); 302 | } 303 | echo "\n"; 304 | 305 | $this->eventManager->trigger(self::ON_AFTER_REQUEST); 306 | } 307 | 308 | public function __destruct() 309 | { 310 | $this->eventManager->trigger(new Event(self::ON_STOP, [ 311 | 'key1' => 'val1' 312 | ])); 313 | } 314 | } 315 | ``` 316 | 317 | ### 2. 此应用的监听器类 318 | 319 | 将每个事件的监听器写一个类,显得有些麻烦。我们可以只写一个类用里面不同的方法来处理不同的事件。 320 | 321 | - 方式一: **类里面存在跟事件名称相同的方法**(`app.start` -> `start()`) 322 | 323 | > 这种方式简单快捷,但是有一定的限制 - 事件名与方法的名称必须相同。 324 | 325 | ```php 326 | 327 | /** 328 | * Class AppListener 329 | * @package Inhere\Event\Examples 330 | */ 331 | class AppListener 332 | { 333 | public function start(EventInterface $event) 334 | { 335 | $pos = __METHOD__; 336 | echo "handle the event {$event->getName()} on the: $pos\n"; 337 | } 338 | 339 | public function beforeRequest(EventInterface $event) 340 | { 341 | $pos = __METHOD__; 342 | echo "handle the event {$event->getName()} on the: $pos\n"; 343 | } 344 | 345 | public function afterRequest(EventInterface $event) 346 | { 347 | $pos = __METHOD__; 348 | echo "handle the event {$event->getName()} on the: $pos\n"; 349 | } 350 | 351 | public function stop(EventInterface $event) 352 | { 353 | $pos = __METHOD__; 354 | echo "handle the event {$event->getName()} on the: $pos\n"; 355 | } 356 | } 357 | ``` 358 | 359 | - 方式二:实现接口 `EventSubscriberInterface` 360 | 361 | 有时候我们并不想将处理方法定义成事件名称一样,想自定义。 362 | 363 | 此时我们可以实现接口 `EventSubscriberInterface`,通过里面的 `getSubscribedEvents()` 来自定义事件和对应的处理方法 364 | 365 | > 运行示例请看 `examples/enum-group.php` 366 | 367 | ```php 368 | /** 369 | * Class EnumGroupListener 370 | * @package Inhere\Event\Examples 371 | */ 372 | class EnumGroupListener implements EventSubscriberInterface 373 | { 374 | const TEST_EVENT = 'test'; 375 | const POST_EVENT = 'post'; 376 | 377 | /** 378 | * 配置事件与对应的处理方法 379 | * @return array 380 | */ 381 | public static function getSubscribedEvents(): array 382 | { 383 | return [ 384 | self::TEST_EVENT => 'onTest', 385 | self::POST_EVENT => ['onPost', ListenerPriority::LOW], // 还可以配置优先级 386 | ]; 387 | } 388 | 389 | public function onTest(EventInterface $event) 390 | { 391 | $pos = __METHOD__; 392 | echo "handle the event {$event->getName()} on the: $pos\n"; 393 | } 394 | 395 | public function onPost(EventInterface $event) 396 | { 397 | $pos = __METHOD__; 398 | echo "handle the event {$event->getName()} on the: $pos\n"; 399 | } 400 | } 401 | 402 | ``` 403 | 404 | ### 3. 添加监听 405 | 406 | ```php 407 | // 这里使用 方式一 408 | $em = new EventManager(); 409 | 410 | // register a group listener 411 | $em->attach('app', new AppListener()); 412 | 413 | // create app 414 | $app = new App($em); 415 | 416 | // run. 417 | $app->run(); 418 | ``` 419 | 420 | ### 4. 运行示例 421 | 422 | 完整的示例代码在 `examples/named-group.php` 中。 423 | 424 | 运行: `php examples/named-group.php` 425 | 426 | 输出: 427 | 428 | ```text 429 | $ php examples/named-group.php 430 | handle the event 'app.start' on the: Inhere\Event\Examples\AppListener::start 431 | handle the event 'app.beforeRequest' on the: Inhere\Event\Examples\AppListener::beforeRequest 432 | request handling .... 433 | handle the event 'app.afterRequest' on the: Inhere\Event\Examples\AppListener::afterRequest 434 | handle the event 'app.stop' on the: Inhere\Event\Examples\AppListener::stop 435 | 436 | ``` 437 | 438 | ## 事件通配符 `*` 439 | 440 | 支持使用事件通配符 `*` 对一组相关的事件进行监听, 分两种。 441 | 442 | 1. `*` 全局的事件通配符。直接对 `*` 添加监听器(`$em->attach('*', 'global_listener')`), 此时所有触发的事件都会被此监听器接收到。 443 | 2. `{prefix}.*` 指定分组事件的监听。eg `$em->attach('db.*', 'db_listener')`, 此时所有触发的以 `db.` 为前缀的事件(eg `db.query` `db.connect`)都会被此监听器接收到。 444 | 445 | > 当然,你在事件到达监听器前停止了本次事件的传播`$event->stopPropagation(true);`,就不会被后面的监听器接收到了。 446 | 447 | 示例,在上面的组事件监听器改下,添加一个 `app.*` 的事件监听。 448 | 449 | ```php 450 | // AppListener 新增一个方法 451 | class AppListener 452 | { 453 | // ... 454 | 455 | public function allEvent(EventInterface $event) 456 | { 457 | $pos = __METHOD__; 458 | echo "handle the event '{$event->getName()}' on the: $pos\n"; 459 | } 460 | } 461 | 462 | // ... 463 | 464 | $em = new EventManager(); 465 | 466 | $groupListener = new AppListener(); 467 | 468 | // register a group listener 469 | $em->attach('app', $groupListener); 470 | 471 | // all `app.` prefix events will be handled by `AppListener::allEvent()` 472 | $em->attach('app.*', [$groupListener, 'allEvent']); 473 | 474 | // create app 475 | $app = new App($em); 476 | 477 | // run. 478 | $app->run(); 479 | ``` 480 | 481 | ### 运行示例 482 | 483 | 运行: `php examples/named-group.php` 484 | 输出:(_可以看到每个事件都经过了`AppListener::allEvent()`的处理_) 485 | 486 | ```text 487 | $ php examples/named-group.php 488 | handle the event 'app.start' on the: Inhere\Event\Examples\AppListener::start 489 | handle the event 'app.start' on the: Inhere\Event\Examples\AppListener::allEvent 490 | handle the event 'app.beforeRequest' on the: Inhere\Event\Examples\AppListener::beforeRequest 491 | handle the event 'app.beforeRequest' on the: Inhere\Event\Examples\AppListener::allEvent 492 | request handling .... 493 | handle the event 'app.afterRequest' on the: Inhere\Event\Examples\AppListener::afterRequest 494 | handle the event 'app.afterRequest' on the: Inhere\Event\Examples\AppListener::allEvent 495 | handle the event 'app.stop' on the: Inhere\Event\Examples\AppListener::stop 496 | handle the event 'app.stop' on the: Inhere\Event\Examples\AppListener::allEvent 497 | 498 | ``` 499 | 500 | ## 事件对象 501 | 502 | 事件对象 - 装载了在触发事件时相关的上下文信息,用户自定义的。 503 | 504 | ### 预先创建一个事件 505 | 506 | - 直接简单的使用类 `Event` 507 | 508 | ```php 509 | $myEvent = new Event('name', 'target', [ 'some params ...' ]); 510 | ``` 511 | 512 | - 使用继承了 `Event` 的子类 513 | 514 | 这样你可以追加自定义数据 515 | 516 | ```php 517 | // create event class 518 | class MessageEvent extends Event 519 | { 520 | protected $name = 'messageSent'; 521 | 522 | // append property ... 523 | public $message; 524 | } 525 | 526 | ``` 527 | 528 | ## License 529 | 530 | MIT 531 | -------------------------------------------------------------------------------- /README_en.md: -------------------------------------------------------------------------------- 1 | # Event Dispatcher 2 | 3 | [![License](https://img.shields.io/packagist/l/inhere/event.svg?style=flat-square)](LICENSE) 4 | [![Php Version](https://img.shields.io/badge/php-%3E=7.1.0-brightgreen.svg?maxAge=2592000)](https://packagist.org/packages/inhere/event) 5 | [![Latest Stable Version](http://img.shields.io/packagist/v/inhere/event.svg)](https://packagist.org/packages/inhere/event) 6 | 7 | > **[中文README](./README.md)** 8 | 9 | Simple, fully functional event management scheduling implementation 10 | 11 | - Implements the [Psr 14](https://github.com/php-fig/fig-standards/blob/master/proposed/event-dispatcher.md) - event dispatcher 12 | - Support for adding multiple listeners to an event 13 | - Support for setting event priority 14 | - Support for fast event group registration 15 | - Support for quick event group monitoring based on event name 16 | - eg Trigger `app.run`, `app.end` will also trigger the `app.*` event 17 | - Support for monitoring of wildcard events 18 | 19 | ## Github 20 | 21 | - **github** https://github.com/inhere/php-event-manager.git 22 | 23 | ## Install 24 | 25 | - by composer require 26 | 27 | ```php 28 | composer require inhere/event 29 | ``` 30 | 31 | - by `composer.json` 32 | 33 | ```json 34 | { 35 | "require": { 36 | "inhere/event": "^1.0" 37 | // "inhere/event": "dev-master" 38 | } 39 | } 40 | ``` 41 | 42 | ### Event dispatcher 43 | 44 | The event dispatcher, also known as the event manager. 45 | 46 | Event registration, listener registration, and dispatcher (triggering) are all managed by it. 47 | 48 | ```php 49 | use Inhere\Event\EventManager; 50 | 51 | $em = new EventManager(); 52 | ``` 53 | 54 | ## Event listener 55 | 56 | listener can be: 57 | 58 | 1. function name 59 | 2. a closure 60 | 3. a class(There are many ways) 61 | 62 | ### 1. Function 63 | 64 | ```php 65 | // ... 66 | 67 | $em->attach(Mailer::EVENT_MESSAGE_SENT, 'my_function'); 68 | ``` 69 | 70 | ### 2. Closure 71 | 72 | ```php 73 | // ... 74 | 75 | $em->attach(Mailer::EVENT_MESSAGE_SENT, function(Event $event) { 76 | // $message = $event->message; 77 | // ... some logic 78 | }); 79 | ``` 80 | 81 | ### 3. Listener class 82 | 83 | #### Method with the same name of the event 84 | 85 | a method with the same name as the event in the class 86 | 87 | > This way you can write multiple event handlers in a class. 88 | 89 | ```php 90 | class ExamListener1 91 | { 92 | public function messageSent(EventInterface $event) 93 | { 94 | echo "handle the event {$event->getName()}\n"; 95 | } 96 | 97 | public function otherEvent(EventInterface $event) 98 | { 99 | echo "handle the event {$event->getName()}\n"; 100 | } 101 | } 102 | 103 | // register 104 | $em->addListener('group name', new ExamListener1); 105 | ``` 106 | 107 | #### A class (with the `__invoke` method) 108 | 109 | > At this point, this class object is equivalent to a closure. 110 | 111 | ```php 112 | class ExamListener2 113 | { 114 | public function __invoke(EventInterface $event) 115 | { 116 | echo "handle the event {$event->getName()}\n"; 117 | } 118 | } 119 | 120 | // register 121 | $em->addListener('event name', new ExamListener2); 122 | ``` 123 | 124 | #### Implements `EventHandlerInterface` 125 | 126 | The `handle()` method is called automatically when triggered. 127 | 128 | ```php 129 | class ExamHandler implements EventHandlerInterface 130 | { 131 | /** 132 | * @param EventInterface $event 133 | * @return mixed 134 | */ 135 | public function handle(EventInterface $event) 136 | { 137 | // TODO: Implement handle() method. 138 | } 139 | } 140 | 141 | // register 142 | $em->addListener('event name', new ExamListener2); 143 | ``` 144 | 145 | #### Implements `EventSubscriberInterface` 146 | 147 | Can customize multiple events in one class, and allow configure priority. 148 | 149 | ```php 150 | /** 151 | * Class EnumGroupListener 152 | * @package Inhere\Event\Examples 153 | */ 154 | class EnumGroupListener implements EventSubscriberInterface 155 | { 156 | const TEST_EVENT = 'test'; 157 | const POST_EVENT = 'post'; 158 | 159 | /** 160 | * Configuration events and corresponding processing methods 161 | * @return array 162 | */ 163 | public static function getSubscribedEvents(): array 164 | { 165 | return [ 166 | self::TEST_EVENT => 'onTest', 167 | // can also configure priority 168 | self::POST_EVENT => ['onPost', ListenerPriority::LOW], 169 | ]; 170 | } 171 | 172 | public function onTest(EventInterface $event) 173 | { 174 | $pos = __METHOD__; 175 | echo "handle the event {$event->getName()} on the: $pos\n"; 176 | } 177 | 178 | public function onPost(EventInterface $event) 179 | { 180 | $pos = __METHOD__; 181 | echo "handle the event {$event->getName()} on the: $pos\n"; 182 | } 183 | } 184 | ``` 185 | 186 | ## Quick use 187 | 188 | ### 1. Prepare 189 | 190 | ```php 191 | // a pre-defined event 192 | class MessageEvent extends Event 193 | { 194 | // append property ... 195 | public $message; 196 | } 197 | 198 | // in the business 199 | class Mailer 200 | { 201 | use EventManagerAwareTrait; 202 | 203 | const EVENT_MESSAGE_SENT = 'messageSent'; 204 | 205 | public function send($message) 206 | { 207 | // ... do send $message ... 208 | 209 | $event = new MessageEvent(self::EVENT_MESSAGE_SENT); 210 | $event->message = $message; 211 | 212 | // will event trigger 213 | $this->eventManager->trigger($event); 214 | } 215 | } 216 | ``` 217 | 218 | ### 2. Binding events and trigger 219 | 220 | ```php 221 | $em = new EventManager(); 222 | 223 | // binding events 224 | $em->attach(Mailer::EVENT_MESSAGE_SENT, 'exam_handler'); 225 | $em->attach(Mailer::EVENT_MESSAGE_SENT, function (EventInterface $event) 226 | { 227 | $pos = __METHOD__; 228 | echo "handle the event {$event->getName()} on the: $pos\n"; 229 | }); 230 | 231 | // add more listeners ... 232 | // give it a higher priority here. 233 | $em->attach(Mailer::EVENT_MESSAGE_SENT, new ExamListener1(), 10); 234 | $em->attach(Mailer::EVENT_MESSAGE_SENT, new ExamListener2()); 235 | $em->attach(Mailer::EVENT_MESSAGE_SENT, new ExamHandler()); 236 | 237 | $mailer = new Mailer(); 238 | $mailer->setEventManager($em); 239 | 240 | // Execution will trigger the event 241 | $mailer->send('hello, world!'); 242 | ``` 243 | 244 | ### 3. Running example 245 | 246 | The complete example code is in `examples/demo.php`. 247 | 248 | Running: `php examples/demo.php` 249 | Output: 250 | 251 | ```text 252 | $ php examples/exam.php 253 | handle the event 'messageSent' on the: ExamListener1::messageSent // Higher priority first call 254 | handle the event 'messageSent' on the: exam_handler 255 | handle the event 'messageSent' on the: {closure} 256 | handle the event 'messageSent' on the: ExamListener2::__invoke 257 | handle the event 'messageSent' on the: Inhere\Event\Examples\ExamHandler::handle 258 | 259 | ``` 260 | 261 | ## Listening to a set of events 262 | 263 | Except for some special events, in an application, most of the events are related, 264 | so we can group the events for easy identification and management. 265 | 266 | - **Event grouping** It is recommended to group related events in the name design 267 | 268 | Example: 269 | 270 | ```text 271 | // Model related: 272 | model.insert 273 | model.update 274 | model.delete 275 | 276 | // DB related: 277 | db.connect 278 | db.disconnect 279 | db.query 280 | 281 | // Application related: 282 | app.start 283 | app.run 284 | app.stop 285 | ``` 286 | 287 | ### 1. A simple example application class 288 | 289 | ```php 290 | 291 | /** 292 | * Class App 293 | * @package Inhere\Event\Examples 294 | */ 295 | class App 296 | { 297 | use EventManagerAwareTrait; 298 | 299 | const ON_START = 'app.start'; 300 | const ON_STOP = 'app.stop'; 301 | const ON_BEFORE_REQUEST = 'app.beforeRequest'; 302 | const ON_AFTER_REQUEST = 'app.afterRequest'; 303 | 304 | public function __construct(EventManager $em) 305 | { 306 | $this->setEventManager($em); 307 | 308 | $this->eventManager->trigger(new Event(self::ON_START, [ 309 | 'key' => 'val' 310 | ])); 311 | } 312 | 313 | public function run() 314 | { 315 | $sleep = 0; 316 | $this->eventManager->trigger(self::ON_BEFORE_REQUEST); 317 | 318 | echo 'request handling '; 319 | while ($sleep <= 3) { 320 | $sleep++; 321 | echo '.'; 322 | sleep(1); 323 | } 324 | echo "\n"; 325 | 326 | $this->eventManager->trigger(self::ON_AFTER_REQUEST); 327 | } 328 | 329 | public function __destruct() 330 | { 331 | $this->eventManager->trigger(new Event(self::ON_STOP, [ 332 | 'key1' => 'val1' 333 | ])); 334 | } 335 | } 336 | ``` 337 | 338 | ### 2. Listener class for this app 339 | 340 | It would be a bit of a hassle to write a class for each event listener. 341 | We can just write a class to handle different events in different ways. 342 | 343 | - Method 1: **There is a method in the class with the same name as the event.**(`app.start` -> `start()`) 344 | 345 | > This method is quick and easy, but with certain restrictions - the name of the event and the method must be the same. 346 | 347 | ```php 348 | /** 349 | * Class AppListener 350 | * @package Inhere\Event\Examples 351 | */ 352 | class AppListener 353 | { 354 | public function start(EventInterface $event) 355 | { 356 | $pos = __METHOD__; 357 | echo "handle the event {$event->getName()} on the: $pos\n"; 358 | } 359 | 360 | public function beforeRequest(EventInterface $event) 361 | { 362 | $pos = __METHOD__; 363 | echo "handle the event {$event->getName()} on the: $pos\n"; 364 | } 365 | 366 | public function afterRequest(EventInterface $event) 367 | { 368 | $pos = __METHOD__; 369 | echo "handle the event {$event->getName()} on the: $pos\n"; 370 | } 371 | 372 | public function stop(EventInterface $event) 373 | { 374 | $pos = __METHOD__; 375 | echo "handle the event {$event->getName()} on the: $pos\n"; 376 | } 377 | } 378 | ``` 379 | 380 | - Method 2:Implementation `EventSubscriberInterface` 381 | 382 | Sometimes we don't want to define the processing method as the event name, we want to customize the processing method name. 383 | 384 | At this point we can implement the interface `EventSubscriberInterface`, 385 | through the `getSubscribedEvents()` inside to customize the event and the corresponding processing method 386 | 387 | ```php 388 | /** 389 | * Class EnumGroupListener 390 | * @package Inhere\Event\Examples 391 | */ 392 | class EnumGroupListener implements EventSubscriberInterface 393 | { 394 | const TEST_EVENT = 'test'; 395 | const POST_EVENT = 'post'; 396 | 397 | /** 398 | * Configuration events and corresponding processing methods 399 | * @return array 400 | */ 401 | public static function getSubscribedEvents(): array 402 | { 403 | return [ 404 | self::TEST_EVENT => 'onTest', 405 | self::POST_EVENT => ['onPost', ListenerPriority::LOW], // setting priority 406 | ]; 407 | } 408 | 409 | public function onTest(EventInterface $event) 410 | { 411 | $pos = __METHOD__; 412 | echo "handle the event {$event->getName()} on the: $pos\n"; 413 | } 414 | 415 | public function onPost(EventInterface $event) 416 | { 417 | $pos = __METHOD__; 418 | echo "handle the event {$event->getName()} on the: $pos\n"; 419 | } 420 | } 421 | ``` 422 | 423 | > Please see the running example `examples/enum-group.php` 424 | 425 | ### 3. Register listener 426 | 427 | ```php 428 | // Note: Use here one way 429 | $em = new EventManager(); 430 | 431 | // register a group listener 432 | $em->attach('app', new AppListener()); 433 | 434 | // create app 435 | $app = new App($em); 436 | 437 | // run. 438 | $app->run(); 439 | ``` 440 | 441 | ### 4. Running example 442 | 443 | The full sample code is in `examples/named-group.php`. 444 | 445 | Running: `php examples/named-group.php` 446 | Output: 447 | 448 | ```text 449 | $ php examples/named-group.php 450 | handle the event 'app.start' on the: Inhere\Event\Examples\AppListener::start 451 | handle the event 'app.beforeRequest' on the: Inhere\Event\Examples\AppListener::beforeRequest 452 | request handling .... 453 | handle the event 'app.afterRequest' on the: Inhere\Event\Examples\AppListener::afterRequest 454 | handle the event 'app.stop' on the: Inhere\Event\Examples\AppListener::stop 455 | 456 | ``` 457 | 458 | ## Event wildcard `*` 459 | 460 | Support for using the event wildcard `*` to listen for a group of related events, divided into two. 461 | 462 | 1. `*` global event wildcard。add listener for `*` (`$em->attach('*', 'global_listener')`), At this point all triggered events will be received by this listener. 463 | 2. `{prefix}.*` Specify listeners for grouping events. eg `$em->attach('db.*', 'db_listener')`, At this point, all events triggered by `db.` (eg `db.query` `db.connect`) will be received by this listener. 464 | 465 | > Of course, you stop the propagation of this event `$event->stopPropagation(true);` 466 | > before the event reaches the listener, and it will not be received by subsequent listeners. 467 | 468 | For example, in the above group event listener, add an event listener for `app.*`. 469 | 470 | ```php 471 | // AppListener 472 | // add new method to listen all events 473 | class AppListener 474 | { 475 | // ... 476 | 477 | public function allEvent(EventInterface $event) 478 | { 479 | $pos = __METHOD__; 480 | echo "handle the event '{$event->getName()}' on the: $pos\n"; 481 | } 482 | } 483 | 484 | // ... 485 | 486 | $em = new EventManager(); 487 | 488 | $groupListener = new AppListener(); 489 | 490 | // register a group listener 491 | $em->attach('app', $groupListener); 492 | 493 | // all `app.` prefix events will be handled by `AppListener::allEvent()` 494 | $em->attach('app.*', [$groupListener, 'allEvent']); 495 | 496 | // create app 497 | $app = new App($em); 498 | 499 | // run. 500 | $app->run(); 501 | ``` 502 | 503 | ### Running example 504 | 505 | Running: `php examples/named-group.php` 506 | Output: (_You can see that each event has been processed by `AppListener::allEvent()`_) 507 | 508 | ```text 509 | $ php examples/named-group.php 510 | handle the event 'app.start' on the: Inhere\Event\Examples\AppListener::start 511 | handle the event 'app.start' on the: Inhere\Event\Examples\AppListener::allEvent 512 | handle the event 'app.beforeRequest' on the: Inhere\Event\Examples\AppListener::beforeRequest 513 | handle the event 'app.beforeRequest' on the: Inhere\Event\Examples\AppListener::allEvent 514 | request handling .... 515 | handle the event 'app.afterRequest' on the: Inhere\Event\Examples\AppListener::afterRequest 516 | handle the event 'app.afterRequest' on the: Inhere\Event\Examples\AppListener::allEvent 517 | handle the event 'app.stop' on the: Inhere\Event\Examples\AppListener::stop 518 | handle the event 'app.stop' on the: Inhere\Event\Examples\AppListener::allEvent 519 | 520 | ``` 521 | 522 | ## Event object 523 | 524 | Event Object - Loads the context information associated with the trigger event, user-defined data. 525 | 526 | ### Create an event in advance 527 | 528 | - Direct and simple use of class `Event` 529 | 530 | ```php 531 | $myEvent = new Event('name', 'target', [ 'some params ...' ]); 532 | ``` 533 | 534 | - Use a subclass that inherits `Event` 535 | 536 | > So you can append custom data 537 | 538 | ```php 539 | // create event class 540 | class MessageEvent extends Event 541 | { 542 | protected $name = 'messageSent'; 543 | 544 | // append property ... 545 | public $message; 546 | } 547 | ``` 548 | 549 | ## License 550 | 551 | [MIT](LICENSE) 552 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "inhere/event", 3 | "type": "library", 4 | "description": "event manager library of the php", 5 | "keywords": [ 6 | "library", 7 | "event", 8 | "event-dispatcher" 9 | ], 10 | "homepage": "https://github.com/inhere/php-event-manager", 11 | "license": "MIT", 12 | "authors": [ 13 | { 14 | "name": "inhere", 15 | "email": "in.798@qq.com", 16 | "homepage": "http://www.yzone.net/" 17 | } 18 | ], 19 | "require": { 20 | "php": ">7.1.0" 21 | }, 22 | "autoload": { 23 | "psr-4": { 24 | "Inhere\\Event\\": "src/" 25 | } 26 | }, 27 | "autoload-dev": { 28 | "psr-4": { 29 | "Inhere\\EventTest\\": "src/" 30 | } 31 | }, 32 | "suggest": { 33 | "inhere/php-validate": "Very lightweight data validate tool library", 34 | "inhere/console": "a lightweight php console application library." 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /examples/App.php: -------------------------------------------------------------------------------- 1 | setEventManager($em); 32 | 33 | $this->eventManager->trigger(new Event(self::ON_START, [ 34 | 'key' => 'val' 35 | ])); 36 | } 37 | 38 | public function run() 39 | { 40 | $sleep = 0; 41 | $this->eventManager->trigger(self::ON_BEFORE_REQUEST); 42 | 43 | echo 'request handling '; 44 | while ($sleep <= 3) { 45 | $sleep++; 46 | echo '.'; 47 | sleep(1); 48 | } 49 | echo "\n"; 50 | 51 | $this->eventManager->trigger(self::ON_AFTER_REQUEST); 52 | } 53 | 54 | public function __destruct() 55 | { 56 | $this->eventManager->trigger(new Event(self::ON_STOP, [ 57 | 'key1' => 'val1' 58 | ])); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /examples/AppListener.php: -------------------------------------------------------------------------------- 1 | getName()}' on the: $pos\n"; 23 | } 24 | 25 | public function beforeRequest(EventInterface $event) 26 | { 27 | $pos = __METHOD__; 28 | echo "handle the event '{$event->getName()}' on the: $pos\n"; 29 | } 30 | 31 | public function afterRequest(EventInterface $event) 32 | { 33 | $pos = __METHOD__; 34 | echo "handle the event '{$event->getName()}' on the: $pos\n"; 35 | } 36 | 37 | public function stop(EventInterface $event) 38 | { 39 | $pos = __METHOD__; 40 | echo "handle the event '{$event->getName()}' on the: $pos\n"; 41 | } 42 | 43 | public function allEvent(EventInterface $event) 44 | { 45 | $pos = __METHOD__; 46 | echo "handle the event '{$event->getName()}' on the: $pos\n"; 47 | } 48 | } -------------------------------------------------------------------------------- /examples/EnumGroupListener.php: -------------------------------------------------------------------------------- 1 | 'onTest', 33 | self::POST_EVENT => ['onPost', ListenerPriority::LOW], 34 | ]; 35 | } 36 | 37 | public function onTest(EventInterface $event) 38 | { 39 | $pos = __METHOD__; 40 | echo "handle the event {$event->getName()} on the: $pos\n"; 41 | } 42 | 43 | public function onPost(EventInterface $event) 44 | { 45 | $pos = __METHOD__; 46 | echo "handle the event {$event->getName()} on the: $pos\n"; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /examples/ExamHandler.php: -------------------------------------------------------------------------------- 1 | getName()}' on the: $pos\n"; 28 | 29 | return true; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /examples/demo.php: -------------------------------------------------------------------------------- 1 | getName()}' on the: $pos \n"; 16 | } 17 | 18 | class ExamListener1 19 | { 20 | public function messageSent(EventInterface $event) 21 | { 22 | $pos = __METHOD__; 23 | 24 | echo "handle the event '{$event->getName()}' on the: $pos \n"; 25 | } 26 | } 27 | 28 | class ExamListener2 29 | { 30 | public function __invoke(EventInterface $event) 31 | { 32 | // $event->stopPropagation(true); 33 | $pos = __METHOD__; 34 | echo "handle the event '{$event->getName()}' on the: $pos\n"; 35 | } 36 | } 37 | 38 | // create event class 39 | class MessageEvent extends Event 40 | { 41 | // append property ... 42 | public $message = 'oo a text'; 43 | } 44 | 45 | class Mailer 46 | { 47 | use EventManagerAwareTrait; 48 | 49 | const EVENT_MESSAGE_SENT = 'messageSent'; 50 | 51 | public function send($message) 52 | { 53 | // ...发送 $message 的逻辑... 54 | 55 | $event = new MessageEvent(self::EVENT_MESSAGE_SENT); 56 | $event->message = $message; 57 | 58 | // trigger event 59 | $this->eventManager->trigger($event); 60 | 61 | // var_dump($event); 62 | } 63 | } 64 | 65 | $em = new EventManager(); 66 | 67 | $em->attach(Mailer::EVENT_MESSAGE_SENT, 'exam_handler'); 68 | $em->attach(Mailer::EVENT_MESSAGE_SENT, function (EventInterface $event) { 69 | $pos = __METHOD__; 70 | echo "handle the event '{$event->getName()}' on the: $pos\n"; 71 | }); 72 | $em->attach(Mailer::EVENT_MESSAGE_SENT, new ExamListener1(), 10); 73 | $em->attach(Mailer::EVENT_MESSAGE_SENT, new ExamListener2()); 74 | $em->attach(Mailer::EVENT_MESSAGE_SENT, new ExamHandler()); 75 | 76 | $em->attach('*', function (EventInterface $event) { 77 | echo "handle the event '{$event->getName()}' on the global listener.\n"; 78 | }); 79 | 80 | $mailer = new Mailer(); 81 | $mailer->setEventManager($em); 82 | 83 | $mailer->send('hello, world!'); 84 | -------------------------------------------------------------------------------- /examples/enum-group.php: -------------------------------------------------------------------------------- 1 | addListener(new EnumGroupListener()); 18 | 19 | $demo = new class 20 | { 21 | use \Inhere\Event\EventManagerAwareTrait; 22 | 23 | public function run() 24 | { 25 | $this->eventManager->trigger(EnumGroupListener::TEST_EVENT); 26 | 27 | echo '.'; 28 | sleep(1); 29 | echo ".\n"; 30 | 31 | $this->eventManager->trigger(EnumGroupListener::POST_EVENT); 32 | } 33 | }; 34 | 35 | $demo->setEventManager($em); 36 | $demo->run(); 37 | -------------------------------------------------------------------------------- /examples/named-group.php: -------------------------------------------------------------------------------- 1 | attach('app', $groupListener); 21 | 22 | // all `app.` prefix events will be handled by `AppListener::allEvent()` 23 | $em->attach('app.*', [$groupListener, 'allEvent']); 24 | 25 | // create app 26 | $app = new App($em); 27 | 28 | // run. 29 | $app->run(); 30 | -------------------------------------------------------------------------------- /examples/test.php: -------------------------------------------------------------------------------- 1 | getName()}\n"; 23 | } 24 | 25 | const ON_DB_UPDATE = 'onDbUpdate'; 26 | 27 | public function onDbUpdate(\Inhere\Event\EventInterface $event) 28 | { 29 | echo "handle the event {$event->getName()}, sql: {$event->getParam('sql')}\n"; 30 | } 31 | }; 32 | 33 | $mgr = new \Inhere\Event\EventManager(); 34 | 35 | // 36 | $mgr->attach('test', $myListener); 37 | $evt = $mgr->trigger('test'); 38 | 39 | 40 | // auto bind method 'onDbUpdate' 41 | $mgr->addListener($myListener); 42 | 43 | $evt1 = $mgr->trigger($myListener::ON_DB_UPDATE, null, ['sql' => 'a sql string']); 44 | 45 | //var_dump($evt1); 46 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | 13 | 14 | test 15 | 16 | 17 | 18 | 19 | 20 | src 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/ClassEvent.php: -------------------------------------------------------------------------------- 1 | bool, // is once event 24 | * ] 25 | */ 26 | private static $events = []; 27 | 28 | /** 29 | * register a event handler 30 | * @param string|mixed $class 31 | * @param mixed $event 32 | * @param callable $handler 33 | */ 34 | public static function on(string $class, string $event, callable $handler) 35 | { 36 | $class = \ltrim($class, '\\'); 37 | 38 | if (self::isSupportedEvent($event)) { 39 | self::$events[$event][$class] = $handler; 40 | } 41 | } 42 | 43 | /** 44 | * trigger event 45 | * @param string $event 46 | * @param array $args 47 | * @return bool 48 | */ 49 | public static function fire(string $event, array $args = []) 50 | { 51 | if (!isset(self::$events[$event])) { 52 | return false; 53 | } 54 | 55 | // call event handlers of the event. 56 | foreach ((array)self::$events[$event] as $cb) { 57 | // return FALSE to stop go on handle. 58 | if (false === $cb(...$args)) { 59 | break; 60 | } 61 | } 62 | 63 | // is a once event, remove it 64 | if (self::$events[$event]) { 65 | return self::removeEvent($event); 66 | } 67 | 68 | return true; 69 | } 70 | 71 | /** 72 | * remove event and it's handlers 73 | * @param $event 74 | * @return bool 75 | */ 76 | public static function off(string $event) 77 | { 78 | return self::removeEvent($event); 79 | } 80 | 81 | /** 82 | * @param $event 83 | * @return bool 84 | */ 85 | public static function removeEvent(string $event): bool 86 | { 87 | if (self::hasEvent($event)) { 88 | unset(self::$events[$event]); 89 | return true; 90 | } 91 | 92 | return false; 93 | } 94 | 95 | /** 96 | * @param $event 97 | * @return bool 98 | */ 99 | public static function hasEvent($event) 100 | { 101 | return isset(self::$events[$event]); 102 | } 103 | 104 | /** 105 | * @param $event 106 | * @return bool 107 | */ 108 | public static function isOnce(string $event): bool 109 | { 110 | if (self::hasEvent($event)) { 111 | return self::$events[$event]; 112 | } 113 | 114 | return false; 115 | } 116 | 117 | /** 118 | * check $name is a supported event name 119 | * @param $event 120 | * @return bool 121 | */ 122 | public static function isSupportedEvent(string $event): bool 123 | { 124 | return $event && \preg_match('/^[a-zA-z][\w-]+$/', $event); 125 | } 126 | 127 | /** 128 | * @return array 129 | */ 130 | public static function getEvents(): array 131 | { 132 | return self::$events; 133 | } 134 | 135 | /** 136 | * @return int 137 | */ 138 | public static function countEvents(): int 139 | { 140 | return \count(self::$events); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/Event.php: -------------------------------------------------------------------------------- 1 | setName($name); 53 | } 54 | 55 | if ($params) { 56 | $this->params = $params; 57 | } 58 | } 59 | 60 | /** 61 | * @param string $name 62 | * @return string 63 | * @throws \InvalidArgumentException 64 | */ 65 | public static function checkName(string $name) 66 | { 67 | $name = \trim($name, '. '); 68 | 69 | if (!$name || \strlen($name) > 64) { 70 | throw new \InvalidArgumentException('Setup the name can be a not empty string of not more than 64 characters!'); 71 | } 72 | 73 | return $name; 74 | } 75 | 76 | /** 77 | * {@inheritdoc} 78 | */ 79 | public function getName(): string 80 | { 81 | return $this->name; 82 | } 83 | 84 | /** 85 | * {@inheritdoc} 86 | * @throws \InvalidArgumentException 87 | */ 88 | public function setName($name) 89 | { 90 | $this->name = self::checkName($name); 91 | } 92 | 93 | /** 94 | * set all params 95 | * @param array $params 96 | */ 97 | public function setParams(array $params) 98 | { 99 | $this->params = $params; 100 | } 101 | 102 | /** 103 | * @param array $params 104 | * @return $this 105 | */ 106 | public function addParams(array $params) 107 | { 108 | $this->params = \array_merge($this->params, $params); 109 | return $this; 110 | } 111 | 112 | /** 113 | * get all param 114 | * @return array 115 | */ 116 | public function getParams(): array 117 | { 118 | return $this->params; 119 | } 120 | 121 | /** 122 | * clear all param 123 | */ 124 | public function clearParams() 125 | { 126 | $this->params = []; 127 | } 128 | 129 | /** 130 | * add a argument 131 | * @param string|int $name 132 | * @param mixed $value 133 | * @return $this 134 | * @throws \InvalidArgumentException 135 | */ 136 | public function addParam($name, $value) 137 | { 138 | if (!isset($this->params[$name])) { 139 | $this->setParam($name, $value); 140 | } 141 | 142 | return $this; 143 | } 144 | 145 | /** 146 | * set a param to the event 147 | * @param string|int $name 148 | * @param mixed $value 149 | * @throws \InvalidArgumentException If the argument name is null. 150 | */ 151 | public function setParam($name, $value) 152 | { 153 | if (null === $name) { 154 | throw new \InvalidArgumentException('The argument name cannot be null.'); 155 | } 156 | 157 | $this->params[$name] = $value; 158 | } 159 | 160 | /** 161 | * @param string|int $name 162 | * @param null $default 163 | * @return null 164 | */ 165 | public function getParam($name, $default = null) 166 | { 167 | return $this->params[$name] ?? $default; 168 | } 169 | 170 | /** 171 | * @param string|int $name 172 | * @return bool 173 | */ 174 | public function hasParam($name): bool 175 | { 176 | return isset($this->params[$name]); 177 | } 178 | 179 | /** 180 | * @param string|int $name 181 | */ 182 | public function removeParam(string $name) 183 | { 184 | if (isset($this->params[$name])) { 185 | unset($this->params[$name]); 186 | } 187 | } 188 | 189 | /** 190 | * Get target/context from which event was triggered 191 | * @return null|string|mixed 192 | */ 193 | public function getTarget() 194 | { 195 | return $this->target; 196 | } 197 | 198 | /** 199 | * Set the event target 200 | * @param null|string|mixed $target 201 | * @return void 202 | */ 203 | public function setTarget($target) 204 | { 205 | $this->target = $target; 206 | } 207 | 208 | /** 209 | * Indicate whether or not to stop propagating this event 210 | * @param bool $flag 211 | */ 212 | public function stopPropagation($flag) 213 | { 214 | $this->stopped = (bool)$flag; 215 | } 216 | 217 | /** 218 | * Has this event indicated event propagation should stop? 219 | * @return bool 220 | */ 221 | public function isPropagationStopped(): bool 222 | { 223 | return $this->stopped; 224 | } 225 | 226 | /** 227 | * @return string 228 | */ 229 | public function serialize(): string 230 | { 231 | return \serialize([$this->name, $this->params, $this->stopped]); 232 | } 233 | 234 | /** 235 | * Unserialize the event. 236 | * @param string $serialized The serialized event. 237 | * @return void 238 | */ 239 | public function unserialize($serialized) 240 | { 241 | // ['allowed_class' => null] 242 | [$this->name, $this->params, $this->stopped] = \unserialize($serialized, ['allowed_class' => null]); 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /src/EventHandlerInterface.php: -------------------------------------------------------------------------------- 1 | (object)EventInterface -- event description 39 | * ] 40 | */ 41 | protected $events = []; 42 | 43 | /** 44 | * Listener storage 45 | * @var ListenerQueue[] 46 | */ 47 | protected $listeners = []; 48 | 49 | /** 50 | * EventManager constructor. 51 | * @param EventManagerInterface|null $parent 52 | * @throws \InvalidArgumentException 53 | */ 54 | public function __construct(EventManagerInterface $parent = null) 55 | { 56 | if ($parent) { 57 | $this->setParent($parent); 58 | } 59 | 60 | $this->basicEvent = new Event; 61 | } 62 | 63 | public function __destruct() 64 | { 65 | $this->clear(); 66 | } 67 | 68 | public function clear() 69 | { 70 | $this->parent = $this->basicEvent = null; 71 | $this->events = $this->listeners = []; 72 | } 73 | 74 | /******************************************************************************* 75 | * Listener manager 76 | ******************************************************************************/ 77 | 78 | /** 79 | * Attaches a listener to an event 80 | * @param string $event the event to attach too 81 | * @param callable|EventHandlerInterface|mixed $callback A callable listener 82 | * @param int $priority the priority at which the $callback executed 83 | * @return bool true on success false on failure 84 | */ 85 | public function attach($event, $callback, $priority = 0) 86 | { 87 | return $this->addListener($callback, [$event => $priority]); 88 | } 89 | 90 | /** 91 | * Detaches a listener from an event 92 | * @param string $event the event to attach too 93 | * @param callable|mixed $callback a callable function 94 | * @return bool true on success false on failure 95 | */ 96 | public function detach($event, $callback): bool 97 | { 98 | $this->removeListener($callback, $event); 99 | return true; 100 | } 101 | 102 | /** 103 | * @param EventSubscriberInterface $object 104 | */ 105 | public function addSubscriber(EventSubscriberInterface $object) 106 | { 107 | $priority = ListenerPriority::NORMAL; 108 | 109 | foreach ($object::getSubscribedEvents() as $name => $conf) { 110 | if (!isset($this->listeners[$name])) { 111 | $this->listeners[$name] = new ListenerQueue; 112 | } 113 | 114 | $queue = $this->listeners[$name]; 115 | 116 | // only handler method name 117 | if (\is_string($conf)) { 118 | $queue->add(LazyListener::create([$object, $conf]), $priority); 119 | // with priority ['onPost', ListenerPriority::LOW] 120 | } elseif (\is_string($conf[0])) { 121 | $queue->add(new LazyListener([$object, $conf[0]]), $conf[1] ?? $priority); 122 | } 123 | } 124 | } 125 | 126 | /** 127 | * Add a listener and associate it to one (multiple) event 128 | * @param \Closure|callback|mixed $listener Listener 129 | * @param array|string|int $definition Event name, priority setting 130 | * Allowed: 131 | * $definition = [ 132 | * 'event name' => priority(int), 133 | * 'event name1' => priority(int), 134 | * ] 135 | * OR 136 | * $definition = [ 137 | * 'event name','event name1', 138 | * ] 139 | * OR 140 | * $definition = 'event name' 141 | * OR 142 | * // The priority of the listener 监听器的优先级 143 | * $definition = 1 144 | * @return bool 145 | */ 146 | public function addListener($listener, $definition = null) 147 | { 148 | // ensure $listener is a object. 149 | if (!\is_object($listener)) { 150 | if (\is_string($listener) && \class_exists($listener)) { 151 | $listener = new $listener; 152 | 153 | // like 'function' OR '[object, method]' 154 | } else { 155 | $listener = LazyListener::create($listener); 156 | } 157 | 158 | // is an EventSubscriber 159 | } elseif ($listener instanceof EventSubscriberInterface) { 160 | $this->addSubscriber($listener); 161 | return true; 162 | } 163 | 164 | $defaultPriority = ListenerPriority::NORMAL; 165 | 166 | if (\is_numeric($definition)) { 167 | $defaultPriority = (int)$definition; 168 | $definition = null; 169 | } elseif (\is_string($definition)) { // 仅是个 事件名称 170 | $definition = [$definition => $defaultPriority]; 171 | } elseif ($definition instanceof EventInterface) { // 仅是个 事件对象,取出名称 172 | $definition = [$definition->getName() => $defaultPriority]; 173 | } 174 | 175 | // Associate a listener to each event 176 | if ($definition) { 177 | foreach ($definition as $name => $priority) { 178 | if (\is_int($name)) { 179 | if (!$priority || !\is_string($priority)) { 180 | continue; 181 | } 182 | 183 | $name = $priority; 184 | $priority = $defaultPriority; 185 | } 186 | 187 | if (!$name = \trim($name, '. ')) { 188 | continue; 189 | } 190 | 191 | if (!isset($this->listeners[$name])) { 192 | $this->listeners[$name] = new ListenerQueue; 193 | } 194 | 195 | $this->listeners[$name]->add($listener, $priority); 196 | } 197 | return true; 198 | } 199 | 200 | return false; 201 | } 202 | 203 | /** 204 | * {@inheritDoc} 205 | * @throws \InvalidArgumentException 206 | */ 207 | public function triggerEvent(EventInterface $event) 208 | { 209 | return $this->trigger($event); 210 | } 211 | 212 | /** 213 | * @param array $events 214 | * @param array $args 215 | * @return array 216 | * @throws \InvalidArgumentException 217 | */ 218 | public function triggerBatch(array $events, array $args = []) 219 | { 220 | $results = []; 221 | 222 | foreach ($events as $event) { 223 | $results[] = $this->trigger($event, null, $args); 224 | } 225 | 226 | return $results; 227 | } 228 | 229 | /** 230 | * Trigger an event 231 | * Can accept an EventInterface or will create one if not passed 232 | * @param string|EventInterface $event 'app.start' 'app.stop' 233 | * @param mixed|string $target It is object or string. 234 | * @param array|mixed $args 235 | * @return EventInterface 236 | * @throws \InvalidArgumentException 237 | */ 238 | public function trigger($event, $target = null, array $args = []) 239 | { 240 | if (!$event instanceof EventInterface) { 241 | $name = (string)$event; 242 | $event = $this->events[$name] ?? $this->wrapperEvent($name); 243 | } 244 | 245 | /** @var EventInterface $event */ 246 | if (!$name = $event->getName()) { 247 | throw new \InvalidArgumentException('The triggered event name cannot be empty!'); 248 | } 249 | 250 | $event->setParams($args); 251 | $event->setTarget($target); 252 | 253 | // Initial value of stop propagation flag should be false 254 | $event->stopPropagation(false); 255 | 256 | // have matched listener 257 | if (isset($this->listeners[$name])) { 258 | $this->triggerListeners($this->listeners[$name], $event); 259 | 260 | if ($event->isPropagationStopped()) { 261 | return $event; 262 | } 263 | } 264 | 265 | // have matched listener in parent 266 | if ($this->parent && ($listenerQueue = $this->parent->getListenerQueue($event))) { 267 | $this->triggerListeners($listenerQueue, $event); 268 | unset($listenerQueue); 269 | } 270 | 271 | // like 'app.start' 'app.db.query' 272 | if ($pos = \strrpos($name, '.')) { 273 | $prefix = \substr($name, 0, $pos); 274 | $method = \substr($name, $pos + 1); 275 | 276 | // have a group listener. eg 'app' 277 | if (isset($this->listeners[$prefix])) { 278 | $this->triggerListeners($this->listeners[$prefix], $event, $method); 279 | 280 | if ($event->isPropagationStopped()) { 281 | return $event; 282 | } 283 | } 284 | 285 | // have a wildcards listener. eg 'app.*' 286 | $wildcardEvent = $prefix . '.*'; 287 | 288 | if (isset($this->listeners[$wildcardEvent])) { 289 | $this->triggerListeners($this->listeners[$wildcardEvent], $event, $method); 290 | 291 | if ($event->isPropagationStopped()) { 292 | return $event; 293 | } 294 | } 295 | } 296 | 297 | // have global wildcards '*' listener. 298 | if (isset($this->listeners['*'])) { 299 | $this->triggerListeners($this->listeners['*'], $event); 300 | } 301 | 302 | return $event; 303 | } 304 | 305 | /** 306 | * @param array|ListenerQueue $listeners 307 | * @param EventInterface $event 308 | * @param string|null $method 309 | */ 310 | protected function triggerListeners($listeners, EventInterface $event, $method = null) 311 | { 312 | // $handled = false; 313 | $name = $event->getName(); 314 | $callable = false === \strpos($name, '.'); 315 | 316 | // 循环调用监听器,处理事件 317 | foreach ($listeners as $listener) { 318 | if ($event->isPropagationStopped()) { 319 | break; 320 | } 321 | 322 | if (\is_object($listener)) { 323 | if ($listener instanceof EventHandlerInterface) { 324 | $listener->handle($event); 325 | } elseif ($method && \method_exists($listener, $method)) { 326 | $listener->$method($event); 327 | } elseif ($callable && \method_exists($listener, $name)) { 328 | $listener->$name($event); 329 | } elseif (\method_exists($listener, '__invoke')) { 330 | $listener($event); 331 | } 332 | } elseif (\is_callable($listener)) { 333 | $listener($event); 334 | } 335 | } 336 | } 337 | 338 | /** 339 | * 是否存在 对事件的 监听队列 340 | * @param EventInterface|string $event 341 | * @return boolean 342 | */ 343 | public function hasListenerQueue($event): bool 344 | { 345 | if ($event instanceof EventInterface) { 346 | $event = $event->getName(); 347 | } 348 | 349 | return isset($this->listeners[$event]); 350 | } 351 | 352 | /** 353 | * @see self::hasListenerQueue() alias method 354 | * @param EventInterface|string $event 355 | * @return boolean 356 | */ 357 | public function hasListeners($event): bool 358 | { 359 | return $this->hasListenerQueue($event); 360 | } 361 | 362 | /** 363 | * 是否存在(对事件的)监听器 364 | * @param object $listener 365 | * @param EventInterface|string $event 366 | * @return bool 367 | */ 368 | public function hasListener($listener, $event = null): bool 369 | { 370 | if ($event) { 371 | if ($event instanceof EventInterface) { 372 | $event = $event->getName(); 373 | } 374 | 375 | if (isset($this->listeners[$event])) { 376 | return $this->listeners[$event]->has($listener); 377 | } 378 | } else { 379 | foreach ($this->listeners as $queue) { 380 | if ($queue->has($listener)) { 381 | return true; 382 | } 383 | } 384 | } 385 | 386 | return false; 387 | } 388 | 389 | /** 390 | * 获取事件的一个监听器的优先级别 391 | * @param $listener 392 | * @param string|EventInterface $event 393 | * @return int|null 394 | */ 395 | public function getListenerPriority($listener, $event) 396 | { 397 | if ($event instanceof EventInterface) { 398 | $event = $event->getName(); 399 | } 400 | 401 | if (isset($this->listeners[$event])) { 402 | return $this->listeners[$event]->getPriority($listener); 403 | } 404 | 405 | return null; 406 | } 407 | 408 | /** 409 | * 获取事件的所有监听器 410 | * @param string|EventInterface $event 411 | * @return ListenerQueue|null 412 | */ 413 | public function getListenerQueue($event) 414 | { 415 | if ($event instanceof EventInterface) { 416 | $event = $event->getName(); 417 | } 418 | 419 | return $this->listeners[$event] ?? null; 420 | } 421 | 422 | /** 423 | * 获取事件的所有监听器 424 | * @param string|EventInterface $event 425 | * @return array 426 | */ 427 | public function getListeners($event): array 428 | { 429 | if ($event instanceof EventInterface) { 430 | $event = $event->getName(); 431 | } 432 | 433 | if (isset($this->listeners[$event])) { 434 | return $this->listeners[$event]->getAll(); 435 | } 436 | 437 | return []; 438 | } 439 | 440 | /** 441 | * 统计获取事件的监听器数量 442 | * @param string|EventInterface $event 443 | * @return int 444 | */ 445 | public function countListeners($event): int 446 | { 447 | if ($event instanceof EventInterface) { 448 | $event = $event->getName(); 449 | } 450 | 451 | return isset($this->listeners[$event]) ? \count($this->listeners[$event]) : 0; 452 | } 453 | 454 | /** 455 | * 移除对某个事件的监听 456 | * @param $listener 457 | * @param null|string|EventInterface $event 458 | * - 为空时,移除监听者队列中所有名为 $listener 的监听者 459 | * - 否则, 则移除对事件 $event 的监听者 460 | */ 461 | public function removeListener($listener, $event = null) 462 | { 463 | if ($event) { 464 | if ($event instanceof EventInterface) { 465 | $event = $event->getName(); 466 | } 467 | 468 | // 存在对这个事件的监听队列 469 | if (isset($this->listeners[$event])) { 470 | $this->listeners[$event]->remove($listener); 471 | } 472 | } else { 473 | foreach ($this->listeners as $queue) { 474 | /** @var $queue ListenerQueue */ 475 | $queue->remove($listener); 476 | } 477 | } 478 | } 479 | 480 | /** 481 | * Clear all listeners for a given event 482 | * @param string|EventInterface $event 483 | * @return void 484 | */ 485 | public function clearListeners($event): void 486 | { 487 | if ($event) { 488 | if ($event instanceof EventInterface) { 489 | $event = $event->getName(); 490 | } 491 | 492 | // 存在对这个事件的监听队列 493 | if (isset($this->listeners[$event])) { 494 | unset($this->listeners[$event]); 495 | } 496 | } 497 | } 498 | 499 | /******************************************************************************* 500 | * Event manager 501 | ******************************************************************************/ 502 | 503 | /** 504 | * 添加一个不存在的事件 505 | * @param EventInterface|string $event | event name 506 | * @param array $params 507 | * @return $this 508 | * @throws \InvalidArgumentException 509 | */ 510 | public function addEvent($event, array $params = []) 511 | { 512 | $event = $this->wrapperEvent($event, null, $params); 513 | 514 | /** @var $event Event */ 515 | if (($event instanceof EventInterface) && !isset($this->events[$event->getName()])) { 516 | $this->events[$event->getName()] = $event; 517 | } 518 | 519 | return $this; 520 | } 521 | 522 | /** 523 | * 设定一个事件处理 524 | * @param string|EventInterface $event 525 | * @param array $params 526 | * @return $this 527 | * @throws \InvalidArgumentException 528 | */ 529 | public function setEvent($event, array $params = []) 530 | { 531 | $event = $this->wrapperEvent($event, null, $params); 532 | 533 | if ($event instanceof EventInterface) { 534 | $this->events[$event->getName()] = $event; 535 | } 536 | 537 | return $this; 538 | } 539 | 540 | /** 541 | * @param string $name 542 | * @param null $default 543 | * @return mixed|null 544 | */ 545 | public function getEvent(string $name, $default = null) 546 | { 547 | return $this->events[$name] ?? $default; 548 | } 549 | 550 | /** 551 | * @param string|EventInterface $event 552 | * @return $this 553 | */ 554 | public function removeEvent($event) 555 | { 556 | if ($event instanceof EventInterface) { 557 | $event = $event->getName(); 558 | } 559 | 560 | if (isset($this->events[$event])) { 561 | unset($this->events[$event]); 562 | } 563 | 564 | return $this; 565 | } 566 | 567 | /** 568 | * @param string|EventInterface $event 569 | * @return bool 570 | */ 571 | public function hasEvent($event): bool 572 | { 573 | if ($event instanceof EventInterface) { 574 | $event = $event->getName(); 575 | } 576 | 577 | return isset($this->events[$event]); 578 | } 579 | 580 | /** 581 | * @return array 582 | */ 583 | public function getEvents(): array 584 | { 585 | return $this->events; 586 | } 587 | 588 | /** 589 | * @param array $events 590 | * @throws \InvalidArgumentException 591 | */ 592 | public function setEvents(array $events) 593 | { 594 | foreach ($events as $key => $event) { 595 | $this->setEvent($event); 596 | } 597 | } 598 | 599 | /** 600 | * @param $event 601 | * @param null|string $target 602 | * @param array $params 603 | * @return EventInterface 604 | */ 605 | public function wrapperEvent($event, $target = null, array $params = []) 606 | { 607 | if (!$event instanceof EventInterface) { 608 | $name = (string)$event; 609 | $event = clone $this->basicEvent; 610 | $event->setName($name); 611 | } 612 | 613 | if ($target) { 614 | $event->setTarget($target); 615 | } 616 | 617 | if ($params) { 618 | $event->setParams($params); 619 | } 620 | 621 | return $event; 622 | } 623 | 624 | /** 625 | * @return int 626 | */ 627 | public function countEvents(): int 628 | { 629 | return \count($this->events); 630 | } 631 | 632 | /** 633 | * @return EventManager 634 | */ 635 | public function getParent(): ?EventManagerInterface 636 | { 637 | return $this->parent; 638 | } 639 | 640 | /** 641 | * @param EventManagerInterface $parent 642 | */ 643 | public function setParent(EventManagerInterface $parent) 644 | { 645 | $this->parent = $parent; 646 | } 647 | 648 | /** 649 | * @return EventInterface 650 | */ 651 | public function getBasicEvent(): EventInterface 652 | { 653 | return $this->basicEvent; 654 | } 655 | 656 | /** 657 | * @param EventInterface $basicEvent 658 | */ 659 | public function setBasicEvent(EventInterface $basicEvent) 660 | { 661 | $this->basicEvent = $basicEvent; 662 | } 663 | } 664 | -------------------------------------------------------------------------------- /src/EventManagerAwareTrait.php: -------------------------------------------------------------------------------- 1 | eventManager && $createIfNotExists) { 30 | $this->setEventManager(new EventManager()); 31 | } 32 | 33 | return $this->eventManager; 34 | } 35 | 36 | /** 37 | * @param EventManager|EventManagerInterface $eventManager 38 | */ 39 | public function setEventManager(EventManagerInterface $eventManager) 40 | { 41 | $this->eventManager = $eventManager; 42 | 43 | if (\method_exists($this, 'attachDefaultListeners')) { 44 | $this->attachDefaultListeners($eventManager); 45 | } 46 | } 47 | 48 | /** 49 | * @param string|EventInterface $event 'app.start' 'app.stop' 50 | * @param mixed|string $target 51 | * @param array|mixed $args 52 | * @return mixed 53 | */ 54 | public function trigger($event, $target = null, array $args = []) 55 | { 56 | if ($this->eventManager) { 57 | return $this->eventManager->trigger($event, $target, $args); 58 | } 59 | 60 | return $event; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/EventManagerInterface.php: -------------------------------------------------------------------------------- 1 | handler, can with priority 25 | // KernelEvents::CONTROLLER => ['onKernelController', ListenerPriority::LOW], 26 | // KernelEvents::VIEW => 'onKernelView', 27 | // ]; 28 | // } 29 | } 30 | -------------------------------------------------------------------------------- /src/Listener/LazyListener.php: -------------------------------------------------------------------------------- 1 | callback = $callback; 41 | } 42 | 43 | /** 44 | * @param EventInterface $event 45 | * @return mixed 46 | */ 47 | public function handle(EventInterface $event) 48 | { 49 | return ($this->callback)($event); 50 | } 51 | 52 | /** 53 | * @return callable 54 | */ 55 | public function getCallback() 56 | { 57 | return $this->callback; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Listener/ListenerPriority.php: -------------------------------------------------------------------------------- 1 | store = new \SplObjectStorage(); 38 | $this->queue = new \SplPriorityQueue(); 39 | } 40 | 41 | /** 42 | * 添加一个监听器, 增加了添加 callback(string|array) 43 | * @param \Closure|callable|\stdClass|mixed $listener 监听器 44 | * @param integer $priority 优先级 45 | * @return $this 46 | */ 47 | public function add($listener, int $priority) 48 | { 49 | // transfer to object. like string/array 50 | if (!\is_object($listener)) { 51 | $listener = new LazyListener($listener); 52 | } 53 | 54 | if (!$this->has($listener)) { 55 | // Compute the internal priority as an array. 计算内部优先级为一个数组。 56 | // @see http://php.net/manual/zh/splpriorityqueue.compare.php#93999 57 | $priorityData = [$priority, $this->counter--]; 58 | 59 | $this->store->attach($listener, $priorityData); 60 | $this->queue->insert($listener, $priorityData); 61 | } 62 | 63 | return $this; 64 | } 65 | 66 | /** 67 | * 删除一个监听器 68 | * @param object $listener 69 | * @return $this 70 | */ 71 | public function remove($listener) 72 | { 73 | if ($this->has($listener)) { 74 | $this->store->detach($listener); 75 | $this->store->rewind(); 76 | 77 | $queue = new \SplPriorityQueue(); 78 | 79 | foreach ($this->store as $otherListener) { 80 | // 优先级信息 @see self::add(). It like `[priority, counter value]` 81 | $priority = $this->store->getInfo(); 82 | $queue->insert($otherListener, $priority); 83 | } 84 | 85 | $this->queue = $queue; 86 | } 87 | 88 | return $this; 89 | } 90 | 91 | /** 92 | * Get the priority of the given listener. 得到指定监听器的优先级 93 | * @param mixed $listener The listener. 94 | * @param int $default The default value to return if the listener doesn't exist. 95 | * @return mixed The listener priority if it exists, null otherwise. 96 | */ 97 | public function getPriority($listener, int $default = null) 98 | { 99 | if ($this->store->contains($listener)) { 100 | // @see self::add(). attach as: `[priority, counter value]` 101 | return $this->store[$listener][0]; 102 | } 103 | 104 | return $default; 105 | } 106 | 107 | /** 108 | * getPriority() alias method 109 | * @param $listener 110 | * @param int $default 111 | * @return mixed 112 | */ 113 | public function getLevel($listener, int $default = null) 114 | { 115 | return $this->getPriority($listener, $default); 116 | } 117 | 118 | /** 119 | * Get all listeners contained in this queue, sorted according to their priority. 120 | * @return mixed[] An array of listeners. 121 | */ 122 | public function getAll() 123 | { 124 | $listeners = []; 125 | 126 | // Get a clone of the queue. 127 | $queue = $this->getIterator(); 128 | 129 | foreach ($queue as $listener) { 130 | $listeners[] = $listener; 131 | } 132 | 133 | unset($queue); 134 | return $listeners; 135 | } 136 | 137 | /** 138 | * @param object $listener 139 | * @return bool 140 | */ 141 | public function has($listener): bool 142 | { 143 | return $this->store->contains($listener); 144 | } 145 | 146 | /** 147 | * @param object $listener 148 | * @return bool 149 | */ 150 | public function exists($listener): bool 151 | { 152 | return $this->has($listener); 153 | } 154 | 155 | /** 156 | * Get the inner queue with its cursor on top of the heap. 157 | * @return \SplPriorityQueue The inner queue. 158 | */ 159 | public function getIterator() 160 | { 161 | // SplPriorityQueue queue is a heap. 162 | $queue = clone $this->queue; 163 | 164 | // rewind pointer. 165 | if (!$queue->isEmpty()) { 166 | $queue->top(); 167 | } 168 | 169 | return $queue; 170 | } 171 | 172 | /** 173 | * {@inheritDoc} 174 | */ 175 | public function count(): int 176 | { 177 | return \count($this->queue); 178 | } 179 | 180 | /** 181 | * clear queue 182 | */ 183 | public function clear(): void 184 | { 185 | $this->queue = $this->store = null; 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /test/EventManagerTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf(EventManagerInterface::class, $em); 27 | $this->assertEmpty($em->getParent()); 28 | $this->assertNotEmpty($em->getBasicEvent()); 29 | $this->assertSame(0, $em->countEvents()); 30 | 31 | $em->setBasicEvent(new Event('new')); 32 | $this->assertNotEmpty($evt = $em->getBasicEvent()); 33 | $this->assertSame('new', $evt->getName()); 34 | 35 | $em1 = new EventManager(); 36 | $em1->setParent($em); 37 | 38 | $this->assertNotEmpty($em1->getParent()); 39 | } 40 | 41 | public function testAttach() 42 | { 43 | $em = new EventManager(); 44 | $l1 = new ExamHandler(); 45 | $em->attach('test', $l1); 46 | $em->attach('test', function () { 47 | // 48 | }); 49 | 50 | $this->assertCount(2, $em->getListeners('test')); 51 | 52 | $em->detach('test', $l1); 53 | $this->assertCount(1, $em->getListeners('test')); 54 | } 55 | 56 | public function testPriority() 57 | { 58 | $l0 = new ExamHandler(); 59 | $l1 = function () { 60 | // 61 | }; 62 | 63 | $em = new EventManager(); 64 | $em->attach('test', $l0); 65 | $em->attach('test', $l1, 5); 66 | 67 | $this->assertEquals(0, $em->getListenerPriority($l0, 'test')); 68 | $this->assertEquals(5, $em->getListenerPriority($l1, 'test')); 69 | } 70 | 71 | public function testTrigger() 72 | { 73 | $l0 = new class 74 | { 75 | public function __invoke(Event $evt) 76 | { 77 | $evt->addParam('key1', 'val1'); 78 | $evt->setParam('key', 'new val'); 79 | } 80 | }; 81 | $l1 = function (EventInterface $evt) { 82 | $evt->setTarget('new target'); 83 | }; 84 | 85 | $em = new EventManager(); 86 | $em->attach('test', $l0); 87 | $em->attach('test', $l1, 5); 88 | 89 | $evt = $em->trigger('test', 'target', ['key' => 'val']); 90 | 91 | $this->assertInstanceOf(EventInterface::class, $evt); 92 | $this->assertEquals('new target', $evt->getTarget()); 93 | $this->assertEquals('new val', $evt->getParam('key')); 94 | $this->assertArrayHasKey('key1', $evt->getParams()); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /test/EventTest.php: -------------------------------------------------------------------------------- 1 | 'val1', 25 | ]); 26 | 27 | // name 28 | $this->assertSame('test', $e->getName()); 29 | 30 | // target 31 | $this->assertNull($e->getTarget()); 32 | $e->setTarget('target'); 33 | $this->assertSame('target', $e->getTarget()); 34 | 35 | // params 36 | $this->assertNotEmpty($e->getParams()); 37 | 38 | // get param 39 | $this->assertSame('val0', $e->getParam(0)); 40 | $this->assertSame('val1', $e->getParam('key1')); 41 | $this->assertNull($e->getParam('not-exist')); 42 | $this->assertSame('def-val', $e->getParam('not-exist', 'def-val')); 43 | 44 | // set param 45 | $this->assertFalse($e->hasParam('key2')); 46 | $e->setParam('key2', 'val2'); 47 | $this->assertTrue($e->hasParam('key2')); 48 | 49 | // add param 50 | $this->assertFalse($e->hasParam('key3')); 51 | $e->addParam('key3', 'val3'); 52 | $this->assertTrue($e->hasParam('key3')); 53 | $e->removeParam('key3'); 54 | $this->assertFalse($e->hasParam('key3')); 55 | 56 | // set params 57 | $e->setParams([ 58 | 'key' => 'val' 59 | ]); 60 | $this->assertCount(1, $e->getParams()); 61 | 62 | // add params 63 | $e->addParams([ 64 | 'key1' => 'val1' 65 | ]); 66 | $this->assertCount(2, $e->getParams()); 67 | 68 | // isPropagationStopped 69 | $this->assertFalse($e->isPropagationStopped()); 70 | $e->stopPropagation(true); 71 | $this->assertTrue($e->isPropagationStopped()); 72 | 73 | $ne = Event::create(); 74 | $this->assertSame('', $ne->getName()); 75 | 76 | // serialize 77 | $data = $e->serialize(); 78 | // unserialize 79 | $ne->unserialize($data); 80 | $this->assertSame('test', $ne->getName()); 81 | 82 | $e->clearParams(); 83 | $this->assertEmpty($e->getParams()); 84 | } 85 | 86 | public function testBadName() 87 | { 88 | $e = new Event(); 89 | 90 | $this->expectException(\InvalidArgumentException::class); 91 | $e->setName(''); 92 | 93 | $this->expectException(\InvalidArgumentException::class); 94 | $e->setParam(null, 'val'); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /test/LazyListenerTest.php: -------------------------------------------------------------------------------- 1 | assertSame('lazy', $e->getName()); 26 | return 'ABC'; 27 | }); 28 | 29 | $this->assertNotEmpty($listener->getCallback()); 30 | $this->assertSame('ABC', $listener->handle(Event::create('lazy'))); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /test/ListenerQueueTest.php: -------------------------------------------------------------------------------- 1 | add($cb0, 0); 31 | $lq->add($cb1, 1); 32 | $lq->add($cb2, 2); 33 | $lq->add($cb3, -2); 34 | $lq->add($cb4, 20); 35 | 36 | $this->assertCount(5, $lq); 37 | $this->assertSame($lq->getPriority($cb4), 20); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /test/boot.php: -------------------------------------------------------------------------------- 1 | $libDir . '/examples/', 17 | 'Inhere\\Event\\' => $libDir . '/src/', 18 | 'Inhere\\EventTest\\' => $libDir . '/test/', 19 | ]; 20 | 21 | spl_autoload_register(function ($class) use ($npMap) { 22 | foreach ($npMap as $np => $dir) { 23 | $file = $dir . str_replace('\\', '/', substr($class, strlen($np))) . '.php'; 24 | 25 | if (file_exists($file)) { 26 | include $file; 27 | } 28 | } 29 | }); 30 | --------------------------------------------------------------------------------