File indexing completed on 2024-05-12 06:03:07

0001 <?php
0002 /**
0003  * Zend Framework
0004  *
0005  * LICENSE
0006  *
0007  * This source file is subject to the new BSD license that is bundled
0008  * with this package in the file LICENSE.txt.
0009  * It is also available through the world-wide-web at this URL:
0010  * http://framework.zend.com/license/new-bsd
0011  * If you did not receive a copy of the license and are unable to
0012  * obtain it through the world-wide-web, please send an email
0013  * to license@zend.com so we can send you a copy immediately.
0014  *
0015  * @category   Zend
0016  * @package    Zend_Stdlib
0017  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0018  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0019  */
0020 
0021 /**
0022  * CallbackHandler
0023  *
0024  * A handler for a event, event, filterchain, etc. Abstracts PHP callbacks,
0025  * primarily to allow for lazy-loading and ensuring availability of default
0026  * arguments (currying).
0027  *
0028  * @category   Zend
0029  * @package    Zend_Stdlib
0030  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0031  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0032  */
0033 class Zend_Stdlib_CallbackHandler
0034 {
0035     /**
0036      * @var string|array PHP callback to invoke
0037      */
0038     protected $callback;
0039 
0040     /**
0041      * Did an error occur when testing the validity of the callback?
0042      * @var bool
0043      */
0044     protected $error = false;
0045 
0046     /**
0047      * Callback metadata, if any
0048      * @var array
0049      */
0050     protected $metadata;
0051 
0052     /**
0053      * Constructor
0054      * 
0055      * @param  string $event Event to which slot is subscribed
0056      * @param  string|array|object $callback PHP callback 
0057      * @param  array $options Options used by the callback handler (e.g., priority)
0058      * @return void
0059      */
0060     public function __construct($callback, array $metadata = array())
0061     {
0062         $this->metadata  = $metadata;
0063         $this->registerCallback($callback);
0064     }
0065 
0066     /**
0067      * Error handler
0068      *
0069      * Used by registerCallback() when calling is_callable() to capture engine warnings.
0070      * 
0071      * @param  int $errno 
0072      * @param  string $errstr 
0073      * @return void
0074      */
0075     public function errorHandler($errno, $errstr)
0076     {
0077         $this->error = true;
0078     }
0079 
0080     /**
0081      * Registers the callback provided in the constructor
0082      *
0083      * If you have pecl/weakref {@see http://pecl.php.net/weakref} installed, 
0084      * this method provides additional behavior.
0085      *
0086      * If a callback is a functor, or an array callback composing an object 
0087      * instance, this method will pass the object to a WeakRef instance prior
0088      * to registering the callback.
0089      * 
0090      * @param  Callable $callback 
0091      * @return void
0092      */
0093     protected function registerCallback($callback)
0094     {
0095         set_error_handler(array($this, 'errorHandler'), E_STRICT);
0096         $callable = is_callable($callback);
0097         restore_error_handler();
0098         if (!$callable || $this->error) {
0099             // require_once 'Zend/Stdlib/Exception/InvalidCallbackException.php';
0100             throw new Zend_Stdlib_Exception_InvalidCallbackException('Invalid callback provided; not callable');
0101         }
0102 
0103         // If pecl/weakref is not installed, simply store the callback and return
0104         set_error_handler(array($this, 'errorHandler'), E_WARNING);
0105         $callable = class_exists('WeakRef');
0106         restore_error_handler();
0107         if (!$callable || $this->error) {
0108             $this->callback = $callback;
0109             return;
0110         }
0111 
0112         // If WeakRef exists, we want to use it.
0113 
0114         // If we have a non-closure object, pass it to WeakRef, and then
0115         // register it.
0116         if (is_object($callback) && !$callback instanceof Closure) {
0117             $this->callback = new WeakRef($callback);
0118             return;
0119         }
0120 
0121         // If we have a string or closure, register as-is
0122         if (!is_array($callback)) {
0123             $this->callback = $callback;
0124             return;
0125         }
0126 
0127         list($target, $method) = $callback;
0128 
0129         // If we have an array callback, and the first argument is not an 
0130         // object, register as-is
0131         if (!is_object($target)) {
0132             $this->callback = $callback;
0133             return;
0134         }
0135 
0136         // We have an array callback with an object as the first argument;
0137         // pass it to WeakRef, and then register the new callback
0138         $target = new WeakRef($target);
0139         $this->callback = array($target, $method);
0140     }
0141 
0142     /**
0143      * Retrieve registered callback
0144      * 
0145      * @return Callable
0146      */
0147     public function getCallback()
0148     {
0149         $callback = $this->callback;
0150 
0151         // String callbacks -- simply return
0152         if (is_string($callback)) {
0153             return $callback;
0154         }
0155 
0156         // WeakRef callbacks -- pull it out of the object and return it
0157         if ($callback instanceof WeakRef) {
0158             return $callback->get();
0159         }
0160 
0161         // Non-WeakRef object callback -- return it
0162         if (is_object($callback)) {
0163             return $callback;
0164         }
0165 
0166         // Array callback with WeakRef object -- retrieve the object first, and 
0167         // then return
0168         list($target, $method) = $callback;
0169         if ($target instanceof WeakRef) {
0170             return array($target->get(), $method);
0171         }
0172 
0173         // Otherwise, return it
0174         return $callback;
0175     }
0176 
0177     /**
0178      * Invoke handler
0179      * 
0180      * @param  array $args Arguments to pass to callback
0181      * @return mixed
0182      */
0183     public function call(array $args = array())
0184     {
0185         $callback = $this->getCallback();
0186 
0187         $isPhp54 = version_compare(PHP_VERSION, '5.4.0rc1', '>=');
0188 
0189         if ($isPhp54 && is_string($callback)) {
0190             $this->validateStringCallbackFor54($callback);
0191         }
0192 
0193         // Minor performance tweak; use call_user_func() until > 3 arguments 
0194         // reached
0195         switch (count($args)) {
0196             case 0:
0197                 if ($isPhp54) {
0198                     return $callback();
0199                 }
0200                 return call_user_func($callback);
0201             case 1:
0202                 if ($isPhp54) {
0203                     return $callback(array_shift($args));
0204                 }
0205                 return call_user_func($callback, array_shift($args));
0206             case 2:
0207                 $arg1 = array_shift($args);
0208                 $arg2 = array_shift($args);
0209                 if ($isPhp54) {
0210                     return $callback($arg1, $arg2);
0211                 }
0212                 return call_user_func($callback, $arg1, $arg2);
0213             case 3:
0214                 $arg1 = array_shift($args);
0215                 $arg2 = array_shift($args);
0216                 $arg3 = array_shift($args);
0217                 if ($isPhp54) {
0218                     return $callback($arg1, $arg2, $arg3);
0219                 }
0220                 return call_user_func($callback, $arg1, $arg2, $arg3);
0221             default:
0222                 return call_user_func_array($callback, $args);
0223         }
0224     }
0225 
0226     /**
0227      * Invoke as functor
0228      * 
0229      * @return mixed
0230      */
0231     public function __invoke()
0232     {
0233         return $this->call(func_get_args());
0234     }
0235 
0236     /**
0237      * Get all callback metadata
0238      * 
0239      * @return array
0240      */
0241     public function getMetadata()
0242     {
0243         return $this->metadata;
0244     }
0245 
0246     /**
0247      * Retrieve a single metadatum
0248      * 
0249      * @param  string $name 
0250      * @return mixed
0251      */
0252     public function getMetadatum($name)
0253     {
0254         if (array_key_exists($name, $this->metadata)) {
0255             return $this->metadata[$name];
0256         }
0257         return null;
0258     }
0259 
0260     /**
0261      * Validate a static method call
0262      *
0263      * Validates that a static method call in PHP 5.4 will actually work
0264      * 
0265      * @param  string $callback 
0266      * @return true
0267      * @throws Zend_Stdlib_Exception_InvalidCallbackException if invalid
0268      */
0269     protected function validateStringCallbackFor54($callback)
0270     {
0271         if (!strstr($callback, '::')) {
0272             return true;
0273         }
0274 
0275         list($class, $method) = explode('::', $callback, 2);
0276 
0277         if (!class_exists($class)) {
0278             // require_once 'Zend/Stdlib/Exception/InvalidCallbackException.php';
0279             throw new Zend_Stdlib_Exception_InvalidCallbackException(sprintf(
0280                 'Static method call "%s" refers to a class that does not exist',
0281                 $callback
0282             ));
0283         }
0284 
0285         $r = new ReflectionClass($class);
0286         if (!$r->hasMethod($method)) {
0287             // require_once 'Zend/Stdlib/Exception/InvalidCallbackException.php';
0288             throw new Zend_Stdlib_Exception_InvalidCallbackException(sprintf(
0289                 'Static method call "%s" refers to a method that does not exist',
0290                 $callback
0291             ));
0292         }
0293         $m = $r->getMethod($method);
0294         if (!$m->isStatic()) {
0295             // require_once 'Zend/Stdlib/Exception/InvalidCallbackException.php';
0296             throw new Zend_Stdlib_Exception_InvalidCallbackException(sprintf(
0297                 'Static method call "%s" refers to a method that is not static',
0298                 $callback
0299             ));
0300         }
0301 
0302         return true;
0303     }
0304 }