Event.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  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. if (isset(self::$_eventWildcards[$name][$class])) {
  157. foreach (self::$_eventWildcards[$name][$class] as $i => $event) {
  158. if ($event[0] === $handler) {
  159. unset(self::$_eventWildcards[$name][$class][$i]);
  160. $removed = true;
  161. }
  162. }
  163. if ($removed) {
  164. self::$_eventWildcards[$name][$class] = array_values(self::$_eventWildcards[$name][$class]);
  165. // remove empty wildcards to save future redundant regex checks :
  166. if (empty(self::$_eventWildcards[$name][$class])) {
  167. unset(self::$_eventWildcards[$name][$class]);
  168. if (empty(self::$_eventWildcards[$name])) {
  169. unset(self::$_eventWildcards[$name]);
  170. }
  171. }
  172. }
  173. }
  174. return $removed;
  175. }
  176. /**
  177. * Detaches all registered class-level event handlers.
  178. * @see on()
  179. * @see off()
  180. * @since 2.0.10
  181. */
  182. public static function offAll()
  183. {
  184. self::$_events = [];
  185. self::$_eventWildcards = [];
  186. }
  187. /**
  188. * Returns a value indicating whether there is any handler attached to the specified class-level event.
  189. * Note that this method will also check all parent classes to see if there is any handler attached
  190. * to the named event.
  191. * @param string|object $class the object or the fully qualified class name specifying the class-level event.
  192. * @param string $name the event name.
  193. * @return bool whether there is any handler attached to the event.
  194. */
  195. public static function hasHandlers($class, $name)
  196. {
  197. if (empty(self::$_eventWildcards) && empty(self::$_events[$name])) {
  198. return false;
  199. }
  200. if (is_object($class)) {
  201. $class = get_class($class);
  202. } else {
  203. $class = ltrim($class, '\\');
  204. }
  205. $classes = array_merge(
  206. [$class],
  207. class_parents($class, true),
  208. class_implements($class, true)
  209. );
  210. // regular events
  211. foreach ($classes as $className) {
  212. if (!empty(self::$_events[$name][$className])) {
  213. return true;
  214. }
  215. }
  216. // wildcard events
  217. foreach (self::$_eventWildcards as $nameWildcard => $classHandlers) {
  218. if (!StringHelper::matchWildcard($nameWildcard, $name, ['escape' => false])) {
  219. continue;
  220. }
  221. foreach ($classHandlers as $classWildcard => $handlers) {
  222. if (empty($handlers)) {
  223. continue;
  224. }
  225. foreach ($classes as $className) {
  226. if (StringHelper::matchWildcard($classWildcard, $className, ['escape' => false])) {
  227. return true;
  228. }
  229. }
  230. }
  231. }
  232. return false;
  233. }
  234. /**
  235. * Triggers a class-level event.
  236. * This method will cause invocation of event handlers that are attached to the named event
  237. * for the specified class and all its parent classes.
  238. * @param string|object $class the object or the fully qualified class name specifying the class-level event.
  239. * @param string $name the event name.
  240. * @param Event $event the event parameter. If not set, a default [[Event]] object will be created.
  241. */
  242. public static function trigger($class, $name, $event = null)
  243. {
  244. $wildcardEventHandlers = [];
  245. foreach (self::$_eventWildcards as $nameWildcard => $classHandlers) {
  246. if (!StringHelper::matchWildcard($nameWildcard, $name)) {
  247. continue;
  248. }
  249. $wildcardEventHandlers = array_merge($wildcardEventHandlers, $classHandlers);
  250. }
  251. if (empty(self::$_events[$name]) && empty($wildcardEventHandlers)) {
  252. return;
  253. }
  254. if ($event === null) {
  255. $event = new static();
  256. }
  257. $event->handled = false;
  258. $event->name = $name;
  259. if (is_object($class)) {
  260. if ($event->sender === null) {
  261. $event->sender = $class;
  262. }
  263. $class = get_class($class);
  264. } else {
  265. $class = ltrim($class, '\\');
  266. }
  267. $classes = array_merge(
  268. [$class],
  269. class_parents($class, true),
  270. class_implements($class, true)
  271. );
  272. foreach ($classes as $class) {
  273. $eventHandlers = [];
  274. foreach ($wildcardEventHandlers as $classWildcard => $handlers) {
  275. if (StringHelper::matchWildcard($classWildcard, $class)) {
  276. $eventHandlers = array_merge($eventHandlers, $handlers);
  277. unset($wildcardEventHandlers[$classWildcard]);
  278. }
  279. }
  280. if (!empty(self::$_events[$name][$class])) {
  281. $eventHandlers = array_merge($eventHandlers, self::$_events[$name][$class]);
  282. }
  283. foreach ($eventHandlers as $handler) {
  284. $event->data = $handler[1];
  285. call_user_func($handler[0], $event);
  286. if ($event->handled) {
  287. return;
  288. }
  289. }
  290. }
  291. }
  292. }