-
Notifications
You must be signed in to change notification settings - Fork 30
/
Copy pathNotificationCenter.php
224 lines (197 loc) · 8.2 KB
/
NotificationCenter.php
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
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
<?php
/**
* Copyright 2017-2019, Optimizely Inc and Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Optimizely\Notification;
use ArgumentCountError;
use Exception;
use Throwable;
use Monolog\Logger;
use Optimizely\ErrorHandler\ErrorHandlerInterface;
use Optimizely\Exceptions\InvalidCallbackArgumentCountException;
use Optimizely\Exceptions\InvalidNotificationTypeException;
use Optimizely\Logger\LoggerInterface;
use Optimizely\Logger\NoOpLogger;
class NotificationCenter
{
private $_notificationId;
private $_notifications;
private $_logger;
private $_errorHandler;
public function __construct(LoggerInterface $logger, ErrorHandlerInterface $errorHandler)
{
$this->_notificationId = 1;
$this->_notifications = [];
foreach (array_values(NotificationType::getAll()) as $type) {
$this->_notifications[$type] = [];
}
$this->_logger = $logger;
$this->_errorHandler = $errorHandler;
}
public function getNotifications()
{
return $this->_notifications;
}
/**
* Adds a notification callback for a notification type to the notification center
*
* @param string $notification_type One of the constants defined in NotificationType
* @param callable $notification_callback A valid PHP callback
*
* @return null Given invalid notification type/callback
* -1 Given callback has been already added
* int Notification ID for the added callback
*/
public function addNotificationListener($notification_type, $notification_callback)
{
if (!NotificationType::isNotificationTypeValid($notification_type)) {
$this->_logger->log(Logger::ERROR, "Invalid notification type.");
$this->_errorHandler->handleError(new InvalidNotificationTypeException('Invalid notification type.'));
return null;
}
if (!is_callable($notification_callback)) {
$this->_logger->log(Logger::ERROR, "Invalid notification callback.");
return null;
}
foreach (array_values($this->_notifications[$notification_type]) as $callback) {
if ($notification_callback == $callback) {
// Note: anonymous methods sent with the same body will be re-added.
// Only variable and object methods can be checked for duplication
$this->_logger->log(Logger::DEBUG, "Callback already added for notification type '{$notification_type}'.");
return -1;
}
}
$this->_notifications[$notification_type][$this->_notificationId] = $notification_callback;
$this->_logger->log(Logger::INFO, "Callback added for notification type '{$notification_type}'.");
$returnVal = $this->_notificationId++;
return $returnVal;
}
/**
* Removes notification callback from the notification center
*
* @param int $notification_id notification ID
*
* @return true When callback removed
* false When no callback found for the given notification ID
*/
public function removeNotificationListener($notification_id)
{
foreach ($this->_notifications as $notification_type => $notifications) {
foreach (array_keys($notifications) as $id) {
if ($notification_id == $id) {
unset($this->_notifications[$notification_type][$id]);
$this->_logger->log(Logger::INFO, "Callback with notification ID '{$notification_id}' has been removed.");
return true;
}
}
}
$this->_logger->log(Logger::DEBUG, "No Callback found with notification ID '{$notification_id}'.");
return false;
}
/**
* Logs deprecation message
*
* @deprecated Use 'clearNotificationListeners' instead.
*/
public function clearNotifications($notification_type)
{
$this->_logger->log(
Logger::WARNING,
"'clearNotifications' is deprecated. Call 'clearNotificationListeners' instead."
);
$this->clearNotificationListeners($notification_type);
}
/**
* Removes all notification callbacks for the given notification type
*
* @param string $notification_type One of the constants defined in NotificationType
*/
public function clearNotificationListeners($notification_type)
{
if (!NotificationType::isNotificationTypeValid($notification_type)) {
$this->_logger->log(Logger::ERROR, "Invalid notification type.");
$this->_errorHandler->handleError(new InvalidNotificationTypeException('Invalid notification type.'));
return;
}
$this->_notifications[$notification_type] = [];
$this->_logger->log(Logger::INFO, "All callbacks for notification type '{$notification_type}' have been removed.");
}
/**
* Logs deprecation message
*
* @deprecated Use 'clearAllNotificationListeners' instead.
*/
public function cleanAllNotifications()
{
$this->_logger->log(
Logger::WARNING,
"'cleanAllNotifications' is deprecated. Call 'clearAllNotificationListeners' instead."
);
$this->clearAllNotificationListeners();
}
/**
* Removes all notifications for all notification types
* from the notification center
*/
public function clearAllNotificationListeners()
{
foreach (array_values(NotificationType::getAll()) as $type) {
$this->_notifications[$type] = [];
}
}
/**
* Executes all registered callbacks for the given notification type
*
* @param string $notification_type One of the constants defined in NotificationType
* @param array $args Array of items to pass as arguments to the callback
*/
public function sendNotifications($notification_type, array $args = [])
{
if (!isset($this->_notifications[$notification_type])) {
// No exception thrown and error logged since this method will be called from
// within the SDK
return;
}
/**
* Note: Before PHP 7, if the callback in call_user_func is called with less number of arguments than expected,
* a warning is issued but the method is still executed with assigning null to the remaining
* arguments. From PHP 7, ArgumentCountError is thrown in such case and the method isn't executed.
* Therefore, we set error handler for warnings so that we raise an exception and notify the
* user that the registered callback has more number of arguments than
* expected. This should be done to keep a consistent behavior across all PHP versions.
*/
set_error_handler(array($this, 'reportArgumentCountError'), E_WARNING);
foreach (array_values($this->_notifications[$notification_type]) as $callback) {
try {
call_user_func_array($callback, $args);
} catch (ArgumentCountError $e) {
$this->reportArgumentCountError();
} catch (Exception $e) {
$this->_logger->log(Logger::ERROR, "Problem calling notify callback.");
}
}
restore_error_handler();
}
/**
* Logs and raises an exception when registered callback expects more number of arguments when executed
*/
public function reportArgumentCountError()
{
$this->_logger->log(Logger::ERROR, "Problem calling notify callback.");
$this->_errorHandler->handleError(
new InvalidCallbackArgumentCountException('Registered callback expects more number of arguments than the actual number')
);
}
}