Xcallable.php 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. <?php
  2. /**
  3. * Hoa
  4. *
  5. *
  6. * @license
  7. *
  8. * New BSD License
  9. *
  10. * Copyright © 2007-2017, Hoa community. All rights reserved.
  11. *
  12. * Redistribution and use in source and binary forms, with or without
  13. * modification, are permitted provided that the following conditions are met:
  14. * * Redistributions of source code must retain the above copyright
  15. * notice, this list of conditions and the following disclaimer.
  16. * * Redistributions in binary form must reproduce the above copyright
  17. * notice, this list of conditions and the following disclaimer in the
  18. * documentation and/or other materials provided with the distribution.
  19. * * Neither the name of the Hoa nor the names of its contributors may be
  20. * used to endorse or promote products derived from this software without
  21. * specific prior written permission.
  22. *
  23. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  24. * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  25. * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  26. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
  27. * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  28. * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  29. * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  30. * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  31. * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  32. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  33. * POSSIBILITY OF SUCH DAMAGE.
  34. */
  35. namespace Hoa\Consistency;
  36. use Hoa\Event;
  37. use Hoa\Stream;
  38. /**
  39. * Class Hoa\Consistency\Xcallable.
  40. *
  41. * Build a callable object, i.e. function, class::method, object->method or
  42. * closure, they all have the same behaviour. This callable is an extension of
  43. * native PHP callable (aka callback) to integrate Hoa's structures.
  44. *
  45. * @copyright Copyright © 2007-2017 Hoa community
  46. * @license New BSD License
  47. */
  48. class Xcallable
  49. {
  50. /**
  51. * Callback, with the PHP format.
  52. *
  53. * @var mixed
  54. */
  55. protected $_callback = null;
  56. /**
  57. * Callable hash.
  58. *
  59. * @var string
  60. */
  61. protected $_hash = null;
  62. /**
  63. * Build a callback.
  64. * Accepted forms:
  65. * * `'function'`,
  66. * * `'class::method'`,
  67. * * `'class', 'method'`,
  68. * * `$object, 'method'`,
  69. * * `$object, ''`,
  70. * * `function (…) { … }`,
  71. * * `['class', 'method']`,
  72. * * `[$object, 'method']`.
  73. *
  74. * @param mixed $call First callable part.
  75. * @param mixed $able Second callable part (if needed).
  76. */
  77. public function __construct($call, $able = '')
  78. {
  79. if ($call instanceof \Closure) {
  80. $this->_callback = $call;
  81. return;
  82. }
  83. if (!is_string($able)) {
  84. throw new Exception(
  85. 'Bad callback form; the able part must be a string.',
  86. 0
  87. );
  88. }
  89. if ('' === $able) {
  90. if (is_string($call)) {
  91. if (false === strpos($call, '::')) {
  92. if (!function_exists($call)) {
  93. throw new Exception(
  94. 'Bad callback form; function %s does not exist.',
  95. 1,
  96. $call
  97. );
  98. }
  99. $this->_callback = $call;
  100. return;
  101. }
  102. list($call, $able) = explode('::', $call);
  103. } elseif (is_object($call)) {
  104. if ($call instanceof Stream\IStream\Out) {
  105. $able = null;
  106. } elseif (method_exists($call, '__invoke')) {
  107. $able = '__invoke';
  108. } else {
  109. throw new Exception(
  110. 'Bad callback form; an object but without a known ' .
  111. 'method.',
  112. 2
  113. );
  114. }
  115. } elseif (is_array($call) && isset($call[0])) {
  116. if (!isset($call[1])) {
  117. return $this->__construct($call[0]);
  118. }
  119. return $this->__construct($call[0], $call[1]);
  120. } else {
  121. throw new Exception(
  122. 'Bad callback form.',
  123. 3
  124. );
  125. }
  126. }
  127. $this->_callback = [$call, $able];
  128. return;
  129. }
  130. /**
  131. * Call the callable.
  132. *
  133. * @param ...
  134. * @return mixed
  135. */
  136. public function __invoke()
  137. {
  138. $arguments = func_get_args();
  139. $valid = $this->getValidCallback($arguments);
  140. return call_user_func_array($valid, $arguments);
  141. }
  142. /**
  143. * Distribute arguments according to an array.
  144. *
  145. * @param array $arguments Arguments.
  146. * @return mixed
  147. */
  148. public function distributeArguments(array $arguments)
  149. {
  150. return call_user_func_array([$this, '__invoke'], $arguments);
  151. }
  152. /**
  153. * Get a valid callback in the PHP meaning.
  154. *
  155. * @param array &$arguments Arguments (could determine method on an
  156. * object if not precised).
  157. * @return mixed
  158. */
  159. public function getValidCallback(array &$arguments = [])
  160. {
  161. $callback = $this->_callback;
  162. $head = null;
  163. if (isset($arguments[0])) {
  164. $head = &$arguments[0];
  165. }
  166. // If method is undetermined, we find it (we understand event bucket and
  167. // stream).
  168. if (null !== $head &&
  169. is_array($callback) &&
  170. null === $callback[1]) {
  171. if ($head instanceof Event\Bucket) {
  172. $head = $head->getData();
  173. }
  174. switch ($type = gettype($head)) {
  175. case 'string':
  176. if (1 === strlen($head)) {
  177. $method = 'writeCharacter';
  178. } else {
  179. $method = 'writeString';
  180. }
  181. break;
  182. case 'boolean':
  183. case 'integer':
  184. case 'array':
  185. $method = 'write' . ucfirst($type);
  186. break;
  187. case 'double':
  188. $method = 'writeFloat';
  189. break;
  190. default:
  191. $method = 'writeAll';
  192. $head = $head . "\n";
  193. }
  194. $callback[1] = $method;
  195. }
  196. return $callback;
  197. }
  198. /**
  199. * Get hash.
  200. * Will produce:
  201. * * function#…;
  202. * * class#…::…;
  203. * * object(…)#…::…;
  204. * * closure(…).
  205. *
  206. * @return string
  207. */
  208. public function getHash()
  209. {
  210. if (null !== $this->_hash) {
  211. return $this->_hash;
  212. }
  213. $_ = &$this->_callback;
  214. if (is_string($_)) {
  215. return $this->_hash = 'function#' . $_;
  216. }
  217. if (is_array($_)) {
  218. return
  219. $this->_hash =
  220. (is_object($_[0])
  221. ? 'object(' . spl_object_hash($_[0]) . ')' .
  222. '#' . get_class($_[0])
  223. : 'class#' . $_[0]) .
  224. '::' .
  225. (null !== $_[1]
  226. ? $_[1]
  227. : '???');
  228. }
  229. return $this->_hash = 'closure(' . spl_object_hash($_) . ')';
  230. }
  231. /**
  232. * Get appropriated reflection instance.
  233. *
  234. * @param ...
  235. * @return \Reflector
  236. */
  237. public function getReflection()
  238. {
  239. $arguments = func_get_args();
  240. $valid = $this->getValidCallback($arguments);
  241. if (is_string($valid)) {
  242. return new \ReflectionFunction($valid);
  243. }
  244. if ($valid instanceof \Closure) {
  245. return new \ReflectionFunction($valid);
  246. }
  247. if (is_array($valid)) {
  248. if (is_string($valid[0])) {
  249. if (false === method_exists($valid[0], $valid[1])) {
  250. return new \ReflectionClass($valid[0]);
  251. }
  252. return new \ReflectionMethod($valid[0], $valid[1]);
  253. }
  254. $object = new \ReflectionObject($valid[0]);
  255. if (null === $valid[1]) {
  256. return $object;
  257. }
  258. return $object->getMethod($valid[1]);
  259. }
  260. }
  261. /**
  262. * Return the hash.
  263. *
  264. * @return string
  265. */
  266. public function __toString()
  267. {
  268. return $this->getHash();
  269. }
  270. }