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