CallbackHandler.php 5.9 KB

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