1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
|
<?php
# Lifter010: TODO
/*
* NotificationCenter.class.php - NotificationCenter class
*
* Copyright (c) 2009 Elmar Ludwig
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*/
// ########################################################################
// MODULE DEFINITION
/** @defgroup notifications Notifications */
/**
* Special Exception that can be thrown to veto an announced change.
* Only some types of events support this (see documentation).
*/
class NotificationVetoException extends Exception
{
}
/**
* The NotificationCenter class is the central event dispatcher
* for Stud.IP. Objects interested in receiving notifications for
* particular events need to register with the NotificationCenter:
*
* NotificationCenter::addObserver($this, 'update', 'shutdown');
*
* Event notifications are sent via the postNotification() method:
*
* NotificationCenter::postNotification('shutdown', $sender);
*/
class NotificationCenter
{
/**
* array of registered notification observers
*/
private static $observers = [];
/**
* Register an object to be notified. The same object may be
* registered several times (e.g. for different notifications).
* The event name may contain shell-style wildcards (like '*').
*
* @param object $observer object to be notified
* @param string $method method that will be called
* @param string $event name of event (may be NULL)
* @param mixed $object subject to observe (may be NULL)
*/
public static function addObserver($observer, $method, $event, $object = NULL)
{
if ($event === NULL) {
$event = '';
}
$predicate = null;
if ($object) {
$predicate = is_callable($object)
? $object
: function ($other) use ($object) {
return $object === $other;
};
}
self::$observers[$event][] = [
'predicate' => $predicate,
'observer' => [$observer, $method]
];
}
/**
* Remove an object registered with the NotificationCenter.
* Trying to remove an observer that was not registered is
* allowed and has no effect.
*
* @param object $observer object to be removed
* @param string $event name of event (may be NULL)
* @param mixed $object subject to observe (may be NULL)
*/
public static function removeObserver($observer, $event = NULL, $object = NULL)
{
if ($event === NULL) {
$events = array_keys(self::$observers);
} else if (isset(self::$observers[$event])) {
$events = [$event];
} else {
return;
}
foreach ($events as $event) {
foreach (self::$observers[$event] as $index => $list) {
if ($object === NULL
|| $list['predicate'] && $list['predicate']($object)) {
if ($list['observer'][0] === $observer) {
unset(self::$observers[$event][$index]);
}
}
}
}
}
/**
* Post an event notification to all registered observers.
* Only observers registered for this event type and subject
* are notified.
*
* @param string $event name of this notification
* @param mixed $object subject of this notification
* @param mixed $user_data additional information (optional)
*
* @throws NotificationVetoException on observer veto
*/
public static function postNotification($event, $object, $user_data = null)
{
$current_observers = [];
foreach (self::$observers as $e => $l) {
if ($e === '' || fnmatch($e, $event, FNM_NOESCAPE)) {
$current_observers = array_merge($current_observers, $l);
}
}
foreach ($current_observers as $list) {
if (!$list['predicate'] || $list['predicate']($object)) {
call_user_func($list['observer'], $event, $object, $user_data);
}
}
}
/**
* Convenience method that uses a jQuery like structure for event
* registration by closures.
*
* @param string $event
* @param Callable $callback
* @param mixed $object
* @since Stud.IP 4.2
*/
public static function on($event, Callable $callback, $object = null)
{
if ($callback instanceof Closure || is_object($callback)) {
static::addObserver($callback, '__invoke', $event, $object);
} elseif (is_array($callback)) {
static::addObserver($callback[0], $callback[1], $event, $object);
} elseif (is_string($callback)) {
throw new Exception('Strings as callable may not be passed to ' . __METHOD__);
}
}
/**
* Convenience method that uses a jQuery like structure for event
* unregistration by closures.
*
* @param string $event
* @param Callable $callback
* @param mixed $object
* @since Stud.IP 4.2
*/
public static function off($event, Callable $callback, $object = null)
{
if ($callback instanceof Closure || is_object($callback)) {
static::removeObserver($callback, $event, $object);
} elseif (is_array($callback)) {
static::removeObserver($callback[0], $event, $object);
} elseif (is_string($callback)) {
throw new Exception('Strings as callable may not be passed to ' . __METHOD__);
}
}
}
|