From 9bce1ba36abea66eee105acb4f4ea187f0f58466 Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Sat, 24 Sep 2011 12:47:06 -0500 Subject: [PATCH] Cleaned up WeakRef support - Added API docs for CallbackHandler::isValid() and CallbackHandler::registerCallback() - Cleaned up commented code in CallbackHandler - Cleaned up API docs for CallbackHandler's validateStringCallback() and validateArrayCallback() methods - Added comment in EventManager::triggerListeners() to note deregistration of invalid listeners - Fixed the logic of CallbackHandler::registerCallback() to immediately register if WeakRef is not available --- src/CallbackHandler.php | 86 ++++++++++++++++++++++++++++++----------- 1 file changed, 64 insertions(+), 22 deletions(-) diff --git a/src/CallbackHandler.php b/src/CallbackHandler.php index be686c626..a42863973 100644 --- a/src/CallbackHandler.php +++ b/src/CallbackHandler.php @@ -66,7 +66,7 @@ class CallbackHandler * Constructor * * @param string $event Event to which slot is subscribed - * @param string|array|object $callback PHP callback (first element may be ) + * @param string|array|object $callback PHP callback * @param array $options Options used by the callback handler (e.g., priority) * @return void */ @@ -77,31 +77,52 @@ public function __construct($event, $callback, array $options = array()) $this->registerCallback($callback); } + /** + * Registers the callback provided in the constructor + * + * If you have pecl/weakref {@see http://pecl.php.net/weakref} installed, + * this method provides additional behavior. + * + * If a callback is a functor, or an array callback composing an object + * instance, this method will pass the object to a WeakRef instance prior + * to registering the callback. See {@link isValid()} for more information + * on how this affects execution. + * + * @param callback $callback + * @return void + */ protected function registerCallback($callback) { - if (is_object($callback) && !$callback instanceof Closure) { - if (class_exists('WeakRef', false)) { - $this->callback = new WeakRef($callback); - } + // If pecl/weakref is not installed, simply register it + if (!class_exists('WeakRef', false)) { + $this->callback = $callback; return; } - if (!is_array($callback)) { - $this->callback = $callback; + // If we have a non-closure object, pass it to WeakRef, and then + // register it. + if (is_object($callback) && !$callback instanceof Closure) { + $this->callback = new WeakRef($callback); return; } - if (!class_exists('WeakRef', false)) { + // If we have a string or closure, register as-is + if (!is_array($callback)) { $this->callback = $callback; return; } list($target, $method) = $callback; + + // If we have an array callback, and the first argument is not an + // object, register as-is if (!is_object($target)) { $this->callback = $callback; return; } + // We have an array callback with an object as the first argument; + // pass it to WeakRef, and then register the new callback $target = new WeakRef($target); $this->callback = array($target, $method); } @@ -120,7 +141,7 @@ public function getEvent() * Retrieve registered callback * * @return Callback - * @throws Exception\InvalidCallbackException + * @throws Exception\InvalidCallbackException If callback is invalid */ public function getCallback() { @@ -185,8 +206,36 @@ public function getOption($name) return null; } + /** + * Is the composed callback valid? + * + * Typically, this method simply checks to see if we have a valid callback. + * In a few situations, it does more. + * + * * If we have a string callback, we pass execution to + * {@link validateStringCallback()}. + * * If we have an object callback, we test to see if that object is a + * WeakRef {@see http://pecl.php.net/weakref}. If so, we return the value + * of its valid() method. Otherwise, we return the result of is_callable(). + * * If we have a callback array with a string in the first position, we + * pass execution to {@link validateArrayCallback()}. + * * If we have a callback array with an object in the first position, we + * test to see if that object is a WeakRef (@see http://pecl.php.net/weakref). + * If so, we return the value of its valid() method. Otherwise, we return + * the result of is_callable() on the callback. + * + * WeakRef is used to allow listeners to go out of scope. This functionality + * is turn-key if you have pecl/weakref installed; otherwise, you will have + * to manually remove listeners before destroying an object referenced in a + * listener. + * + * @return bool + */ public function isValid() { + // If we've already tested this, we can move on. Note: if a callback + // composes a WeakRef, this will never get set, and thus result in + // validation on each call. if ($this->isValidCallback) { return $this->callback; } @@ -197,7 +246,7 @@ public function isValid() return $this->validateStringCallback($callback); } - if ($callback instanceof \WeakRef) { + if ($callback instanceof WeakRef) { return $callback->valid(); } @@ -211,7 +260,7 @@ public function isValid() } list($target, $method) = $callback; - if ($target instanceof \WeakRef) { + if ($target instanceof WeakRef) { if (!$target->valid()) { return false; } @@ -228,39 +277,35 @@ public function isValid() * valid class name; if so, determine if the object is invokable. * * @param string $callback - * @return Callback - * @throws Exception\InvalidCallbackException + * @return bool */ protected function validateStringCallback($callback) { if (is_callable($callback)) { $this->isValidCallback = true; - return $callback; + return true; } if (!class_exists($callback)) { - // throw new Exception\InvalidCallbackException('Provided callback is not a function or a class'); return false; } // check __invoke before instantiating if (!method_exists($callback, '__invoke')) { - // throw new Exception\InvalidCallbackException('Class provided as a callback does not implement __invoke'); return false; } $object = new $callback(); $this->callback = $object; $this->isValidCallback = true; - return $object; + return true; } /** * Validate an array callback * * @param array $callback - * @return callback - * @throws Exception\InvalidCallbackException + * @return bool */ protected function validateArrayCallback(array $callback) { @@ -271,7 +316,6 @@ protected function validateArrayCallback(array $callback) // Dealing with a class/method callback, and class provided is a string classname if (!class_exists($context)) { - // throw new Exception\InvalidCallbackException('Class provided in callback does not exist'); return false; } @@ -280,7 +324,6 @@ protected function validateArrayCallback(array $callback) if (!$r->hasMethod($method)) { // Explicit method does not exist if (!$r->hasMethod('__callStatic') && !$r->hasMethod('__call')) { - // throw new Exception\InvalidCallbackException('Class provided in callback does not define the method requested'); return false; } @@ -317,7 +360,6 @@ protected function validateArrayCallback(array $callback) return $callback; } - // throw new Exception\InvalidCallbackException('Method provided in callback does not exist in object'); return false; } }