2323 */
2424namespace Zend \Stdlib ;
2525
26+ use Closure ,
27+ WeakRef ;
28+
2629/**
2730 * CallbackHandler
2831 *
@@ -63,15 +66,65 @@ class CallbackHandler
6366 * Constructor
6467 *
6568 * @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
6770 * @param array $options Options used by the callback handler (e.g., priority)
6871 * @return void
6972 */
7073 public function __construct ($ event , $ callback , array $ options = array ())
7174 {
7275 $ this ->event = $ event ;
73- $ this ->callback = $ callback ;
7476 $ 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 );
75128 }
76129
77130 /**
@@ -88,26 +141,33 @@ public function getEvent()
88141 * Retrieve registered callback
89142 *
90143 * @return Callback
91- * @throws Exception\InvalidCallbackException
144+ * @throws Exception\InvalidCallbackException If callback is invalid
92145 */
93146 public function getCallback ()
94147 {
95- if ($ this ->isValidCallback ) {
96- return $ this -> callback ;
148+ if (! $ this ->isValid () ) {
149+ throw new Exception \ InvalidCallbackException ( ' Invalid callback provided; not callable ' ) ;
97150 }
98151
99152 $ callback = $ this ->callback ;
100153 if (is_string ($ callback )) {
101- return $ this -> validateStringCallback ( $ callback) ;
154+ return $ callback ;
102155 }
103- if (is_array ($ callback )) {
104- return $ this ->validateArrayCallback ($ callback );
156+
157+ if ($ callback instanceof WeakRef) {
158+ return $ callback ->get ();
105159 }
106- if ( is_callable ( $ callback )) {
107- $ this -> isValidCallback = true ;
160+
161+ if ( is_object ( $ callback )) {
108162 return $ callback ;
109163 }
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 ;
111171 }
112172
113173 /**
@@ -146,44 +206,106 @@ public function getOption($name)
146206 return null ;
147207 }
148208
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+
149273 /**
150274 * Validate a string callback
151275 *
152276 * Check first if the string provided is callable. If not see if it is a
153277 * valid class name; if so, determine if the object is invokable.
154278 *
155279 * @param string $callback
156- * @return Callback
157- * @throws Exception\InvalidCallbackException
280+ * @return bool
158281 */
159282 protected function validateStringCallback ($ callback )
160283 {
161284 if (is_callable ($ callback )) {
162285 $ this ->isValidCallback = true ;
163- return $ callback ;
286+ return true ;
164287 }
165288
166289 if (!class_exists ($ callback )) {
167- throw new Exception \ InvalidCallbackException ( ' Provided callback is not a function or a class ' ) ;
290+ return false ;
168291 }
169292
170293 // check __invoke before instantiating
171294 if (!method_exists ($ callback , '__invoke ' )) {
172- throw new Exception \ InvalidCallbackException ( ' Class provided as a callback does not implement __invoke ' ) ;
295+ return false ;
173296 }
174297 $ object = new $ callback ();
175298
176299 $ this ->callback = $ object ;
177300 $ this ->isValidCallback = true ;
178- return $ object ;
301+ return true ;
179302 }
180303
181304 /**
182305 * Validate an array callback
183306 *
184307 * @param array $callback
185- * @return callback
186- * @throws Exception\InvalidCallbackException
308+ * @return bool
187309 */
188310 protected function validateArrayCallback (array $ callback )
189311 {
@@ -194,15 +316,15 @@ protected function validateArrayCallback(array $callback)
194316 // Dealing with a class/method callback, and class provided is a string classname
195317
196318 if (!class_exists ($ context )) {
197- throw new Exception \ InvalidCallbackException ( ' Class provided in callback does not exist ' ) ;
319+ return false ;
198320 }
199321
200322 // We need to determine if we need to instantiate the class first
201323 $ r = new \ReflectionClass ($ context );
202324 if (!$ r ->hasMethod ($ method )) {
203325 // Explicit method does not exist
204326 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 ;
206328 }
207329
208330 if ($ r ->hasMethod ('__callStatic ' )) {
@@ -238,7 +360,6 @@ protected function validateArrayCallback(array $callback)
238360 return $ callback ;
239361 }
240362
241-
242- throw new Exception \InvalidCallbackException ('Method provided in callback does not exist in object ' );
363+ return false ;
243364 }
244365}
0 commit comments