web/lib/Zend/Stdlib/CallbackHandler.php
changeset 808 6b6c2214f778
child 1230 68c69c656a2c
equal deleted inserted replaced
807:877f952ae2bd 808:6b6c2214f778
       
     1 <?php
       
     2 /**
       
     3  * Zend Framework
       
     4  *
       
     5  * LICENSE
       
     6  *
       
     7  * This source file is subject to the new BSD license that is bundled
       
     8  * with this package in the file LICENSE.txt.
       
     9  * It is also available through the world-wide-web at this URL:
       
    10  * http://framework.zend.com/license/new-bsd
       
    11  * If you did not receive a copy of the license and are unable to
       
    12  * obtain it through the world-wide-web, please send an email
       
    13  * to license@zend.com so we can send you a copy immediately.
       
    14  *
       
    15  * @category   Zend
       
    16  * @package    Zend_Stdlib
       
    17  * @copyright  Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
       
    18  * @license    http://framework.zend.com/license/new-bsd     New BSD License
       
    19  */
       
    20 
       
    21 /**
       
    22  * CallbackHandler
       
    23  *
       
    24  * A handler for a event, event, filterchain, etc. Abstracts PHP callbacks,
       
    25  * primarily to allow for lazy-loading and ensuring availability of default
       
    26  * arguments (currying).
       
    27  *
       
    28  * @category   Zend
       
    29  * @package    Zend_Stdlib
       
    30  * @copyright  Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
       
    31  * @license    http://framework.zend.com/license/new-bsd     New BSD License
       
    32  */
       
    33 class Zend_Stdlib_CallbackHandler
       
    34 {
       
    35     /**
       
    36      * @var string|array PHP callback to invoke
       
    37      */
       
    38     protected $callback;
       
    39 
       
    40     /**
       
    41      * Did an error occur when testing the validity of the callback?
       
    42      * @var bool
       
    43      */
       
    44     protected $error = false;
       
    45 
       
    46     /**
       
    47      * Callback metadata, if any
       
    48      * @var array
       
    49      */
       
    50     protected $metadata;
       
    51 
       
    52     /**
       
    53      * Constructor
       
    54      * 
       
    55      * @param  string $event Event to which slot is subscribed
       
    56      * @param  string|array|object $callback PHP callback 
       
    57      * @param  array $options Options used by the callback handler (e.g., priority)
       
    58      * @return void
       
    59      */
       
    60     public function __construct($callback, array $metadata = array())
       
    61     {
       
    62         $this->metadata  = $metadata;
       
    63         $this->registerCallback($callback);
       
    64     }
       
    65 
       
    66     /**
       
    67      * Error handler
       
    68      *
       
    69      * Used by registerCallback() when calling is_callable() to capture engine warnings.
       
    70      * 
       
    71      * @param  int $errno 
       
    72      * @param  string $errstr 
       
    73      * @return void
       
    74      */
       
    75     public function errorHandler($errno, $errstr)
       
    76     {
       
    77         $this->error = true;
       
    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.
       
    89      * 
       
    90      * @param  Callable $callback 
       
    91      * @return void
       
    92      */
       
    93     protected function registerCallback($callback)
       
    94     {
       
    95         set_error_handler(array($this, 'errorHandler'), E_STRICT);
       
    96         $callable = is_callable($callback);
       
    97         restore_error_handler();
       
    98         if (!$callable || $this->error) {
       
    99             require_once 'Zend/Stdlib/Exception/InvalidCallbackException.php';
       
   100             throw new Zend_Stdlib_Exception_InvalidCallbackException('Invalid callback provided; not callable');
       
   101         }
       
   102 
       
   103         // If pecl/weakref is not installed, simply store the callback and return
       
   104         if (!class_exists('WeakRef')) {
       
   105             $this->callback = $callback;
       
   106             return;
       
   107         }
       
   108 
       
   109         // If WeakRef exists, we want to use it.
       
   110 
       
   111         // If we have a non-closure object, pass it to WeakRef, and then
       
   112         // register it.
       
   113         if (is_object($callback) && !$callback instanceof Closure) {
       
   114             $this->callback = new WeakRef($callback);
       
   115             return;
       
   116         }
       
   117 
       
   118         // If we have a string or closure, register as-is
       
   119         if (!is_array($callback)) {
       
   120             $this->callback = $callback;
       
   121             return;
       
   122         }
       
   123 
       
   124         list($target, $method) = $callback;
       
   125 
       
   126         // If we have an array callback, and the first argument is not an 
       
   127         // object, register as-is
       
   128         if (!is_object($target)) {
       
   129             $this->callback = $callback;
       
   130             return;
       
   131         }
       
   132 
       
   133         // We have an array callback with an object as the first argument;
       
   134         // pass it to WeakRef, and then register the new callback
       
   135         $target = new WeakRef($target);
       
   136         $this->callback = array($target, $method);
       
   137     }
       
   138 
       
   139     /**
       
   140      * Retrieve registered callback
       
   141      * 
       
   142      * @return Callable
       
   143      */
       
   144     public function getCallback()
       
   145     {
       
   146         $callback = $this->callback;
       
   147 
       
   148         // String callbacks -- simply return
       
   149         if (is_string($callback)) {
       
   150             return $callback;
       
   151         }
       
   152 
       
   153         // WeakRef callbacks -- pull it out of the object and return it
       
   154         if ($callback instanceof WeakRef) {
       
   155             return $callback->get();
       
   156         }
       
   157 
       
   158         // Non-WeakRef object callback -- return it
       
   159         if (is_object($callback)) {
       
   160             return $callback;
       
   161         }
       
   162 
       
   163         // Array callback with WeakRef object -- retrieve the object first, and 
       
   164         // then return
       
   165         list($target, $method) = $callback;
       
   166         if ($target instanceof WeakRef) {
       
   167             return array($target->get(), $method);
       
   168         }
       
   169 
       
   170         // Otherwise, return it
       
   171         return $callback;
       
   172     }
       
   173 
       
   174     /**
       
   175      * Invoke handler
       
   176      * 
       
   177      * @param  array $args Arguments to pass to callback
       
   178      * @return mixed
       
   179      */
       
   180     public function call(array $args = array())
       
   181     {
       
   182         $callback = $this->getCallback();
       
   183 
       
   184         $isPhp54 = version_compare(PHP_VERSION, '5.4.0rc1', '>=');
       
   185 
       
   186         if ($isPhp54 && is_string($callback)) {
       
   187             $this->validateStringCallbackFor54($callback);
       
   188         }
       
   189 
       
   190         // Minor performance tweak; use call_user_func() until > 3 arguments 
       
   191         // reached
       
   192         switch (count($args)) {
       
   193             case 0:
       
   194                 if ($isPhp54) {
       
   195                     return $callback();
       
   196                 }
       
   197                 return call_user_func($callback);
       
   198             case 1:
       
   199                 if ($isPhp54) {
       
   200                     return $callback(array_shift($args));
       
   201                 }
       
   202                 return call_user_func($callback, array_shift($args));
       
   203             case 2:
       
   204                 $arg1 = array_shift($args);
       
   205                 $arg2 = array_shift($args);
       
   206                 if ($isPhp54) {
       
   207                     return $callback($arg1, $arg2);
       
   208                 }
       
   209                 return call_user_func($callback, $arg1, $arg2);
       
   210             case 3:
       
   211                 $arg1 = array_shift($args);
       
   212                 $arg2 = array_shift($args);
       
   213                 $arg3 = array_shift($args);
       
   214                 if ($isPhp54) {
       
   215                     return $callback($arg1, $arg2, $arg3);
       
   216                 }
       
   217                 return call_user_func($callback, $arg1, $arg2, $arg3);
       
   218             default:
       
   219                 return call_user_func_array($callback, $args);
       
   220         }
       
   221     }
       
   222 
       
   223     /**
       
   224      * Invoke as functor
       
   225      * 
       
   226      * @return mixed
       
   227      */
       
   228     public function __invoke()
       
   229     {
       
   230         return $this->call(func_get_args());
       
   231     }
       
   232 
       
   233     /**
       
   234      * Get all callback metadata
       
   235      * 
       
   236      * @return array
       
   237      */
       
   238     public function getMetadata()
       
   239     {
       
   240         return $this->metadata;
       
   241     }
       
   242 
       
   243     /**
       
   244      * Retrieve a single metadatum
       
   245      * 
       
   246      * @param  string $name 
       
   247      * @return mixed
       
   248      */
       
   249     public function getMetadatum($name)
       
   250     {
       
   251         if (array_key_exists($name, $this->metadata)) {
       
   252             return $this->metadata[$name];
       
   253         }
       
   254         return null;
       
   255     }
       
   256 
       
   257     /**
       
   258      * Validate a static method call
       
   259      *
       
   260      * Validates that a static method call in PHP 5.4 will actually work
       
   261      * 
       
   262      * @param  string $callback 
       
   263      * @return true
       
   264      * @throws Zend_Stdlib_Exception_InvalidCallbackException if invalid
       
   265      */
       
   266     protected function validateStringCallbackFor54($callback)
       
   267     {
       
   268         if (!strstr($callback, '::')) {
       
   269             return true;
       
   270         }
       
   271 
       
   272         list($class, $method) = explode('::', $callback, 2);
       
   273 
       
   274         if (!class_exists($class)) {
       
   275             require_once 'Zend/Stdlib/Exception/InvalidCallbackException.php';
       
   276             throw new Zend_Stdlib_Exception_InvalidCallbackException(sprintf(
       
   277                 'Static method call "%s" refers to a class that does not exist',
       
   278                 $callback
       
   279             ));
       
   280         }
       
   281 
       
   282         $r = new ReflectionClass($class);
       
   283         if (!$r->hasMethod($method)) {
       
   284             require_once 'Zend/Stdlib/Exception/InvalidCallbackException.php';
       
   285             throw new Zend_Stdlib_Exception_InvalidCallbackException(sprintf(
       
   286                 'Static method call "%s" refers to a method that does not exist',
       
   287                 $callback
       
   288             ));
       
   289         }
       
   290         $m = $r->getMethod($method);
       
   291         if (!$m->isStatic()) {
       
   292             require_once 'Zend/Stdlib/Exception/InvalidCallbackException.php';
       
   293             throw new Zend_Stdlib_Exception_InvalidCallbackException(sprintf(
       
   294                 'Static method call "%s" refers to a method that is not static',
       
   295                 $callback
       
   296             ));
       
   297         }
       
   298 
       
   299         return true;
       
   300     }
       
   301 }