23
23
*/
24
24
namespace Zend \Stdlib ;
25
25
26
+ use Closure ,
27
+ WeakRef ;
28
+
26
29
/**
27
30
* CallbackHandler
28
31
*
@@ -63,15 +66,65 @@ class CallbackHandler
63
66
* Constructor
64
67
*
65
68
* @param string $event Event to which slot is subscribed
66
- * @param string|array|object $callback PHP callback (first element may be )
69
+ * @param string|array|object $callback PHP callback
67
70
* @param array $options Options used by the callback handler (e.g., priority)
68
71
* @return void
69
72
*/
70
73
public function __construct ($ event , $ callback , array $ options = array ())
71
74
{
72
75
$ this ->event = $ event ;
73
- $ this ->callback = $ callback ;
74
76
$ this ->options = $ options ;
77
+ $ this ->registerCallback ($ callback );
78
+ }
79
+
80
+ /**
81
+ * Registers the callback provided in the constructor
82
+ *
83
+ * If you have pecl/weakref {@see http://pecl.php.net/weakref} installed,
84
+ * this method provides additional behavior.
85
+ *
86
+ * If a callback is a functor, or an array callback composing an object
87
+ * instance, this method will pass the object to a WeakRef instance prior
88
+ * to registering the callback. See {@link isValid()} for more information
89
+ * on how this affects execution.
90
+ *
91
+ * @param callback $callback
92
+ * @return void
93
+ */
94
+ protected function registerCallback ($ callback )
95
+ {
96
+ // If pecl/weakref is not installed, simply register it
97
+ if (!class_exists ('WeakRef ' , false )) {
98
+ $ this ->callback = $ callback ;
99
+ return ;
100
+ }
101
+
102
+ // If we have a non-closure object, pass it to WeakRef, and then
103
+ // register it.
104
+ if (is_object ($ callback ) && !$ callback instanceof Closure) {
105
+ $ this ->callback = new WeakRef ($ callback );
106
+ return ;
107
+ }
108
+
109
+ // If we have a string or closure, register as-is
110
+ if (!is_array ($ callback )) {
111
+ $ this ->callback = $ callback ;
112
+ return ;
113
+ }
114
+
115
+ list ($ target , $ method ) = $ callback ;
116
+
117
+ // If we have an array callback, and the first argument is not an
118
+ // object, register as-is
119
+ if (!is_object ($ target )) {
120
+ $ this ->callback = $ callback ;
121
+ return ;
122
+ }
123
+
124
+ // We have an array callback with an object as the first argument;
125
+ // pass it to WeakRef, and then register the new callback
126
+ $ target = new WeakRef ($ target );
127
+ $ this ->callback = array ($ target , $ method );
75
128
}
76
129
77
130
/**
@@ -88,26 +141,33 @@ public function getEvent()
88
141
* Retrieve registered callback
89
142
*
90
143
* @return Callback
91
- * @throws Exception\InvalidCallbackException
144
+ * @throws Exception\InvalidCallbackException If callback is invalid
92
145
*/
93
146
public function getCallback ()
94
147
{
95
- if ($ this ->isValidCallback ) {
96
- return $ this -> callback ;
148
+ if (! $ this ->isValid () ) {
149
+ throw new Exception \ InvalidCallbackException ( ' Invalid callback provided; not callable ' ) ;
97
150
}
98
151
99
152
$ callback = $ this ->callback ;
100
153
if (is_string ($ callback )) {
101
- return $ this -> validateStringCallback ( $ callback) ;
154
+ return $ callback ;
102
155
}
103
- if (is_array ($ callback )) {
104
- return $ this ->validateArrayCallback ($ callback );
156
+
157
+ if ($ callback instanceof WeakRef) {
158
+ return $ callback ->get ();
105
159
}
106
- if ( is_callable ( $ callback )) {
107
- $ this -> isValidCallback = true ;
160
+
161
+ if ( is_object ( $ callback )) {
108
162
return $ callback ;
109
163
}
110
- throw new Exception \InvalidCallbackException ('Invalid callback provided; not callable ' );
164
+
165
+ list ($ target , $ method ) = $ callback ;
166
+ if ($ target instanceof WeakRef) {
167
+ return array ($ target ->get (), $ method );
168
+ }
169
+
170
+ return $ callback ;
111
171
}
112
172
113
173
/**
@@ -146,44 +206,106 @@ public function getOption($name)
146
206
return null ;
147
207
}
148
208
209
+ /**
210
+ * Is the composed callback valid?
211
+ *
212
+ * Typically, this method simply checks to see if we have a valid callback.
213
+ * In a few situations, it does more.
214
+ *
215
+ * * If we have a string callback, we pass execution to
216
+ * {@link validateStringCallback()}.
217
+ * * If we have an object callback, we test to see if that object is a
218
+ * WeakRef {@see http://pecl.php.net/weakref}. If so, we return the value
219
+ * of its valid() method. Otherwise, we return the result of is_callable().
220
+ * * If we have a callback array with a string in the first position, we
221
+ * pass execution to {@link validateArrayCallback()}.
222
+ * * If we have a callback array with an object in the first position, we
223
+ * test to see if that object is a WeakRef (@see http://pecl.php.net/weakref).
224
+ * If so, we return the value of its valid() method. Otherwise, we return
225
+ * the result of is_callable() on the callback.
226
+ *
227
+ * WeakRef is used to allow listeners to go out of scope. This functionality
228
+ * is turn-key if you have pecl/weakref installed; otherwise, you will have
229
+ * to manually remove listeners before destroying an object referenced in a
230
+ * listener.
231
+ *
232
+ * @return bool
233
+ */
234
+ public function isValid ()
235
+ {
236
+ // If we've already tested this, we can move on. Note: if a callback
237
+ // composes a WeakRef, this will never get set, and thus result in
238
+ // validation on each call.
239
+ if ($ this ->isValidCallback ) {
240
+ return $ this ->callback ;
241
+ }
242
+
243
+ $ callback = $ this ->callback ;
244
+
245
+ if (is_string ($ callback )) {
246
+ return $ this ->validateStringCallback ($ callback );
247
+ }
248
+
249
+ if ($ callback instanceof WeakRef) {
250
+ return $ callback ->valid ();
251
+ }
252
+
253
+ if (is_object ($ callback ) && is_callable ($ callback )) {
254
+ $ this ->isValidCallback = true ;
255
+ return true ;
256
+ }
257
+
258
+ if (!is_array ($ callback )) {
259
+ return false ;
260
+ }
261
+
262
+ list ($ target , $ method ) = $ callback ;
263
+ if ($ target instanceof WeakRef) {
264
+ if (!$ target ->valid ()) {
265
+ return false ;
266
+ }
267
+ $ target = $ target ->get ();
268
+ return is_callable (array ($ target , $ method ));
269
+ }
270
+ return $ this ->validateArrayCallback ($ callback );
271
+ }
272
+
149
273
/**
150
274
* Validate a string callback
151
275
*
152
276
* Check first if the string provided is callable. If not see if it is a
153
277
* valid class name; if so, determine if the object is invokable.
154
278
*
155
279
* @param string $callback
156
- * @return Callback
157
- * @throws Exception\InvalidCallbackException
280
+ * @return bool
158
281
*/
159
282
protected function validateStringCallback ($ callback )
160
283
{
161
284
if (is_callable ($ callback )) {
162
285
$ this ->isValidCallback = true ;
163
- return $ callback ;
286
+ return true ;
164
287
}
165
288
166
289
if (!class_exists ($ callback )) {
167
- throw new Exception \ InvalidCallbackException ( ' Provided callback is not a function or a class ' ) ;
290
+ return false ;
168
291
}
169
292
170
293
// check __invoke before instantiating
171
294
if (!method_exists ($ callback , '__invoke ' )) {
172
- throw new Exception \ InvalidCallbackException ( ' Class provided as a callback does not implement __invoke ' ) ;
295
+ return false ;
173
296
}
174
297
$ object = new $ callback ();
175
298
176
299
$ this ->callback = $ object ;
177
300
$ this ->isValidCallback = true ;
178
- return $ object ;
301
+ return true ;
179
302
}
180
303
181
304
/**
182
305
* Validate an array callback
183
306
*
184
307
* @param array $callback
185
- * @return callback
186
- * @throws Exception\InvalidCallbackException
308
+ * @return bool
187
309
*/
188
310
protected function validateArrayCallback (array $ callback )
189
311
{
@@ -194,15 +316,15 @@ protected function validateArrayCallback(array $callback)
194
316
// Dealing with a class/method callback, and class provided is a string classname
195
317
196
318
if (!class_exists ($ context )) {
197
- throw new Exception \ InvalidCallbackException ( ' Class provided in callback does not exist ' ) ;
319
+ return false ;
198
320
}
199
321
200
322
// We need to determine if we need to instantiate the class first
201
323
$ r = new \ReflectionClass ($ context );
202
324
if (!$ r ->hasMethod ($ method )) {
203
325
// Explicit method does not exist
204
326
if (!$ r ->hasMethod ('__callStatic ' ) && !$ r ->hasMethod ('__call ' )) {
205
- throw new Exception \ InvalidCallbackException ( ' Class provided in callback does not define the method requested ' ) ;
327
+ return false ;
206
328
}
207
329
208
330
if ($ r ->hasMethod ('__callStatic ' )) {
@@ -238,7 +360,6 @@ protected function validateArrayCallback(array $callback)
238
360
return $ callback ;
239
361
}
240
362
241
-
242
- throw new Exception \InvalidCallbackException ('Method provided in callback does not exist in object ' );
363
+ return false ;
243
364
}
244
365
}
0 commit comments