Event.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. <?php
  2. /**
  3. * @link http://www.yiiframework.com/
  4. * @copyright Copyright (c) 2008 Yii Software LLC
  5. * @license http://www.yiiframework.com/license/
  6. */
  7. namespace yii\base;
  8. use yii\helpers\StringHelper;
  9. /**
  10. * Event is the base class for all event classes.
  11. *
  12. * It encapsulates the parameters associated with an event.
  13. * The [[sender]] property describes who raises the event.
  14. * And the [[handled]] property indicates if the event is handled.
  15. * If an event handler sets [[handled]] to be `true`, the rest of the
  16. * uninvoked handlers will no longer be called to handle the event.
  17. *
  18. * Additionally, when attaching an event handler, extra data may be passed
  19. * and be available via the [[data]] property when the event handler is invoked.
  20. *
  21. * For more details and usage information on Event, see the [guide article on events](guide:concept-events).
  22. *
  23. * @author Qiang Xue <qiang.xue@gmail.com>
  24. * @since 2.0
  25. */
  26. class Event extends BaseObject
  27. {
  28. /**
  29. * @var string the event name. This property is set by [[Component::trigger()]] and [[trigger()]].
  30. * Event handlers may use this property to check what event it is handling.
  31. */
  32. public $name;
  33. /**
  34. * @var object the sender of this event. If not set, this property will be
  35. * set as the object whose `trigger()` method is called.
  36. * This property may also be a `null` when this event is a
  37. * class-level event which is triggered in a static context.
  38. */
  39. public $sender;
  40. /**
  41. * @var bool whether the event is handled. Defaults to `false`.
  42. * When a handler sets this to be `true`, the event processing will stop and
  43. * ignore the rest of the uninvoked event handlers.
  44. */
  45. public $handled = false;
  46. /**
  47. * @var mixed the data that is passed to [[Component::on()]] when attaching an event handler.
  48. * Note that this varies according to which event handler is currently executing.
  49. */
  50. public $data;
  51. /**
  52. * @var array contains all globally registered event handlers.
  53. */
  54. private static $_events = [];
  55. /**
  56. * @var array the globally registered event handlers attached for wildcard patterns (event name wildcard => handlers)
  57. * @since 2.0.14
  58. */
  59. private static $_eventWildcards = [];
  60. /**
  61. * Attaches an event handler to a class-level event.
  62. *
  63. * When a class-level event is triggered, event handlers attached
  64. * to that class and all parent classes will be invoked.
  65. *
  66. * For example, the following code attaches an event handler to `ActiveRecord`'s
  67. * `afterInsert` event:
  68. *
  69. * ```php
  70. * Event::on(ActiveRecord::className(), ActiveRecord::EVENT_AFTER_INSERT, function ($event) {
  71. * Yii::trace(get_class($event->sender) . ' is inserted.');
  72. * });
  73. * ```
  74. *
  75. * The handler will be invoked for EVERY successful ActiveRecord insertion.
  76. *
  77. * Since 2.0.14 you can specify either class name or event name as a wildcard pattern:
  78. *
  79. * ```php
  80. * Event::on('app\models\db\*', '*Insert', function ($event) {
  81. * Yii::trace(get_class($event->sender) . ' is inserted.');
  82. * });
  83. * ```
  84. *
  85. * For more details about how to declare an event handler, please refer to [[Component::on()]].
  86. *
  87. * @param string $class the fully qualified class name to which the event handler needs to attach.
  88. * @param string $name the event name.
  89. * @param callable $handler the event handler.
  90. * @param mixed $data the data to be passed to the event handler when the event is triggered.
  91. * When the event handler is invoked, this data can be accessed via [[Event::data]].
  92. * @param bool $append whether to append new event handler to the end of the existing
  93. * handler list. If `false`, the new handler will be inserted at the beginning of the existing
  94. * handler list.
  95. * @see off()
  96. */
  97. public static function on($class, $name, $handler, $data = null, $append = true)
  98. {
  99. $class = ltrim($class, '\\');
  100. if (strpos($class, '*') !== false || strpos($name, '*') !== false) {
  101. if ($append || empty(self::$_eventWildcards[$name][$class])) {
  102. self::$_eventWildcards[$name][$class][] = [$handler, $data];
  103. } else {
  104. array_unshift(self::$_eventWildcards[$name][$class], [$handler, $data]);
  105. }
  106. return;
  107. }
  108. if ($append || empty(self::$_events[$name][$class])) {
  109. self::$_events[$name][$class][] = [$handler, $data];
  110. } else {
  111. array_unshift(self::$_events[$name][$class], [$handler, $data]);
  112. }
  113. }
  114. /**
  115. * Detaches an event handler from a class-level event.
  116. *
  117. * This method is the opposite of [[on()]].
  118. *
  119. * Note: in case wildcard pattern is passed for class name or event name, only the handlers registered with this
  120. * wildcard will be removed, while handlers registered with plain names matching this wildcard will remain.
  121. *
  122. * @param string $class the fully qualified class name from which the event handler needs to be detached.
  123. * @param string $name the event name.
  124. * @param callable $handler the event handler to be removed.
  125. * If it is `null`, all handlers attached to the named event will be removed.
  126. * @return bool whether a handler is found and detached.
  127. * @see on()
  128. */
  129. public static function off($class, $name, $handler = null)
  130. {
  131. $class = ltrim($class, '\\');
  132. if (empty(self::$_events[$name][$class]) && empty(self::$_eventWildcards[$name][$class])) {
  133. return false;
  134. }
  135. if ($handler === null) {
  136. unset(self::$_events[$name][$class]);
  137. unset(self::$_eventWildcards[$name][$class]);
  138. return true;
  139. }
  140. // plain event names
  141. if (isset(self::$_events[$name][$class])) {
  142. $removed = false;
  143. foreach (self::$_events[$name][$class] as $i => $event) {
  144. if ($event[0] === $handler) {
  145. unset(self::$_events[$name][$class][$i]);
  146. $removed = true;
  147. }
  148. }
  149. if ($removed) {
  150. self::$_events[$name][$class] = array_values(self::$_events[$name][$class]);
  151. return $removed;
  152. }
  153. }
  154. // wildcard event names
  155. $removed = false;
  156. foreach (self::$_eventWildcards[$name][$class] as $i => $event) {
  157. if ($event[0] === $handler) {
  158. unset(self::$_eventWildcards[$name][$class][$i]);
  159. $removed = true;
  160. }
  161. }
  162. if ($removed) {
  163. self::$_eventWildcards[$name][$class] = array_values(self::$_eventWildcards[$name][$class]);
  164. // remove empty wildcards to save future redundant regex checks :
  165. if (empty(self::$_eventWildcards[$name][$class])) {
  166. unset(self::$_eventWildcards[$name][$class]);
  167. if (empty(self::$_eventWildcards[$name])) {
  168. unset(self::$_eventWildcards[$name]);
  169. }
  170. }
  171. }
  172. return $removed;
  173. }
  174. /**
  175. * Detaches all registered class-level event handlers.
  176. * @see on()
  177. * @see off()
  178. * @since 2.0.10
  179. */
  180. public static function offAll()
  181. {
  182. self::$_events = [];
  183. self::$_eventWildcards = [];
  184. }
  185. /**
  186. * Returns a value indicating whether there is any handler attached to the specified class-level event.
  187. * Note that this method will also check all parent classes to see if there is any handler attached
  188. * to the named event.
  189. * @param string|object $class the object or the fully qualified class name specifying the class-level event.
  190. * @param string $name the event name.
  191. * @return bool whether there is any handler attached to the event.
  192. */
  193. public static function hasHandlers($class, $name)
  194. {
  195. if (empty(self::$_eventWildcards) && empty(self::$_events[$name])) {
  196. return false;
  197. }
  198. if (is_object($class)) {
  199. $class = get_class($class);
  200. } else {
  201. $class = ltrim($class, '\\');
  202. }
  203. $classes = array_merge(
  204. [$class],
  205. class_parents($class, true),
  206. class_implements($class, true)
  207. );
  208. // regular events
  209. foreach ($classes as $class) {
  210. if (!empty(self::$_events[$name][$class])) {
  211. return true;
  212. }
  213. }
  214. // wildcard events
  215. foreach (self::$_eventWildcards as $nameWildcard => $classHandlers) {
  216. if (!StringHelper::matchWildcard($nameWildcard, $name)) {
  217. continue;
  218. }
  219. foreach ($classHandlers as $classWildcard => $handlers) {
  220. if (empty($handlers)) {
  221. continue;
  222. }
  223. foreach ($classes as $class) {
  224. if (!StringHelper::matchWildcard($classWildcard, $class)) {
  225. return true;
  226. }
  227. }
  228. }
  229. }
  230. return false;
  231. }
  232. /**
  233. * Triggers a class-level event.
  234. * This method will cause invocation of event handlers that are attached to the named event
  235. * for the specified class and all its parent classes.
  236. * @param string|object $class the object or the fully qualified class name specifying the class-level event.
  237. * @param string $name the event name.
  238. * @param Event $event the event parameter. If not set, a default [[Event]] object will be created.
  239. */
  240. public static function trigger($class, $name, $event = null)
  241. {
  242. $wildcardEventHandlers = [];
  243. foreach (self::$_eventWildcards as $nameWildcard => $classHandlers) {
  244. if (!StringHelper::matchWildcard($nameWildcard, $name)) {
  245. continue;
  246. }
  247. $wildcardEventHandlers = array_merge($wildcardEventHandlers, $classHandlers);
  248. }
  249. if (empty(self::$_events[$name]) && empty($wildcardEventHandlers)) {
  250. return;
  251. }
  252. if ($event === null) {
  253. $event = new static();
  254. }
  255. $event->handled = false;
  256. $event->name = $name;
  257. if (is_object($class)) {
  258. if ($event->sender === null) {
  259. $event->sender = $class;
  260. }
  261. $class = get_class($class);
  262. } else {
  263. $class = ltrim($class, '\\');
  264. }
  265. $classes = array_merge(
  266. [$class],
  267. class_parents($class, true),
  268. class_implements($class, true)
  269. );
  270. foreach ($classes as $class) {
  271. $eventHandlers = [];
  272. foreach ($wildcardEventHandlers as $classWildcard => $handlers) {
  273. if (StringHelper::matchWildcard($classWildcard, $class)) {
  274. $eventHandlers = array_merge($eventHandlers, $handlers);
  275. unset($wildcardEventHandlers[$classWildcard]);
  276. }
  277. }
  278. if (!empty(self::$_events[$name][$class])) {
  279. $eventHandlers = array_merge($eventHandlers, self::$_events[$name][$class]);
  280. }
  281. foreach ($eventHandlers as $handler) {
  282. $event->data = $handler[1];
  283. call_user_func($handler[0], $event);
  284. if ($event->handled) {
  285. return;
  286. }
  287. }
  288. }
  289. }
  290. }