CallbackHandler.php 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. <?php
  2. /**
  3. * Zend Framework (http://framework.zend.com/)
  4. *
  5. * @link http://github.com/zendframework/zf2 for the canonical source repository
  6. * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com)
  7. * @license http://framework.zend.com/license/new-bsd New BSD License
  8. */
  9. namespace Zend\Stdlib;
  10. use Closure;
  11. use ReflectionClass;
  12. /**
  13. * CallbackHandler
  14. *
  15. * A handler for a event, event, filterchain, etc. Abstracts PHP callbacks,
  16. * primarily to allow for lazy-loading and ensuring availability of default
  17. * arguments (currying).
  18. */
  19. class CallbackHandler
  20. {
  21. /**
  22. * @var string|array|callable PHP callback to invoke
  23. */
  24. protected $callback;
  25. /**
  26. * Callback metadata, if any
  27. * @var array
  28. */
  29. protected $metadata;
  30. /**
  31. * PHP version is greater as 5.4rc1?
  32. * @var bool
  33. */
  34. protected static $isPhp54;
  35. /**
  36. * Constructor
  37. *
  38. * @param string|array|object|callable $callback PHP callback
  39. * @param array $metadata Callback metadata
  40. */
  41. public function __construct($callback, array $metadata = array())
  42. {
  43. $this->metadata = $metadata;
  44. $this->registerCallback($callback);
  45. }
  46. /**
  47. * Registers the callback provided in the constructor
  48. *
  49. * @param callable $callback
  50. * @throws Exception\InvalidCallbackException
  51. * @return void
  52. */
  53. protected function registerCallback($callback)
  54. {
  55. if (!is_callable($callback)) {
  56. throw new Exception\InvalidCallbackException('Invalid callback provided; not callable');
  57. }
  58. $this->callback = $callback;
  59. }
  60. /**
  61. * Retrieve registered callback
  62. *
  63. * @return callable
  64. */
  65. public function getCallback()
  66. {
  67. return $this->callback;
  68. }
  69. /**
  70. * Invoke handler
  71. *
  72. * @param array $args Arguments to pass to callback
  73. * @return mixed
  74. */
  75. public function call(array $args = array())
  76. {
  77. $callback = $this->getCallback();
  78. // Minor performance tweak, if the callback gets called more than once
  79. if (!isset(static::$isPhp54)) {
  80. static::$isPhp54 = version_compare(PHP_VERSION, '5.4.0rc1', '>=');
  81. }
  82. $argCount = count($args);
  83. if (static::$isPhp54 && is_string($callback)) {
  84. $result = $this->validateStringCallbackFor54($callback);
  85. if ($result !== true && $argCount <= 3) {
  86. $callback = $result;
  87. // Minor performance tweak, if the callback gets called more
  88. // than once
  89. $this->callback = $result;
  90. }
  91. }
  92. // Minor performance tweak; use call_user_func() until > 3 arguments
  93. // reached
  94. switch ($argCount) {
  95. case 0:
  96. if (static::$isPhp54) {
  97. return $callback();
  98. }
  99. return call_user_func($callback);
  100. case 1:
  101. if (static::$isPhp54) {
  102. return $callback(array_shift($args));
  103. }
  104. return call_user_func($callback, array_shift($args));
  105. case 2:
  106. $arg1 = array_shift($args);
  107. $arg2 = array_shift($args);
  108. if (static::$isPhp54) {
  109. return $callback($arg1, $arg2);
  110. }
  111. return call_user_func($callback, $arg1, $arg2);
  112. case 3:
  113. $arg1 = array_shift($args);
  114. $arg2 = array_shift($args);
  115. $arg3 = array_shift($args);
  116. if (static::$isPhp54) {
  117. return $callback($arg1, $arg2, $arg3);
  118. }
  119. return call_user_func($callback, $arg1, $arg2, $arg3);
  120. default:
  121. return call_user_func_array($callback, $args);
  122. }
  123. }
  124. /**
  125. * Invoke as functor
  126. *
  127. * @return mixed
  128. */
  129. public function __invoke()
  130. {
  131. return $this->call(func_get_args());
  132. }
  133. /**
  134. * Get all callback metadata
  135. *
  136. * @return array
  137. */
  138. public function getMetadata()
  139. {
  140. return $this->metadata;
  141. }
  142. /**
  143. * Retrieve a single metadatum
  144. *
  145. * @param string $name
  146. * @return mixed
  147. */
  148. public function getMetadatum($name)
  149. {
  150. if (array_key_exists($name, $this->metadata)) {
  151. return $this->metadata[$name];
  152. }
  153. return null;
  154. }
  155. /**
  156. * Validate a static method call
  157. *
  158. * Validates that a static method call in PHP 5.4 will actually work
  159. *
  160. * @param string $callback
  161. * @return true|array
  162. * @throws Exception\InvalidCallbackException if invalid
  163. */
  164. protected function validateStringCallbackFor54($callback)
  165. {
  166. if (!strstr($callback, '::')) {
  167. return true;
  168. }
  169. list($class, $method) = explode('::', $callback, 2);
  170. if (!class_exists($class)) {
  171. throw new Exception\InvalidCallbackException(sprintf(
  172. 'Static method call "%s" refers to a class that does not exist',
  173. $callback
  174. ));
  175. }
  176. $r = new ReflectionClass($class);
  177. if (!$r->hasMethod($method)) {
  178. throw new Exception\InvalidCallbackException(sprintf(
  179. 'Static method call "%s" refers to a method that does not exist',
  180. $callback
  181. ));
  182. }
  183. $m = $r->getMethod($method);
  184. if (!$m->isStatic()) {
  185. throw new Exception\InvalidCallbackException(sprintf(
  186. 'Static method call "%s" refers to a method that is not static',
  187. $callback
  188. ));
  189. }
  190. // returning a non boolean value may not be nice for a validate method,
  191. // but that allows the usage of a static string callback without using
  192. // the call_user_func function.
  193. return array($class, $method);
  194. }
  195. }