| 1 |
|
<?php |
| 2 |
|
/** |
| 3 |
|
* Xyster Framework |
| 4 |
|
* |
| 5 |
|
* This source file is subject to the new BSD license that is bundled |
| 6 |
|
* with this package in the file LICENSE.txt. |
| 7 |
|
* It is also available through the world-wide-web at this URL: |
| 8 |
|
* http://www.opensource.org/licenses/bsd-license.php |
| 9 |
|
* |
| 10 |
|
* @category Xyster |
| 11 |
|
* @package Xyster_Type |
| 12 |
|
* @copyright Copyright LibreWorks, LLC (http://libreworks.net) |
| 13 |
|
* @license http://www.opensource.org/licenses/bsd-license.php New BSD License |
| 14 |
|
* @version $Id: Builder.php 418 2010-10-18 21:40:08Z jonathanhawk $ |
| 15 |
|
*/ |
| 16 |
|
namespace Xyster\Type\Proxy; |
| 17 |
|
use Xyster\Type\Type; |
| 18 |
|
/** |
| 19 |
|
* A mediator for setting and getting values from a named field |
| 20 |
|
* |
| 21 |
|
* @category Xyster |
| 22 |
|
* @package Xyster_Type |
| 23 |
|
* @copyright Copyright LibreWorks, LLC (http://libreworks.net) |
| 24 |
|
* @license http://www.opensource.org/licenses/bsd-license.php New BSD License |
| 25 |
|
*/ |
| 26 |
|
class Builder |
| 27 |
|
{ |
| 28 |
|
protected static $_types = array(); |
| 29 |
|
protected static $_cache = true; |
| 30 |
|
|
| 31 |
|
protected $_parent; |
| 32 |
|
protected $_interfaces; |
| 33 |
|
protected $_handler; |
| 34 |
|
protected $_callConstructor = false; |
| 35 |
|
protected $_className; |
| 36 |
|
|
| 37 |
|
/** |
| 38 |
|
* Creates a proxy class and returns an instance of it |
| 39 |
|
* |
| 40 |
|
* @param array $args Optional. Arguments to pass to the constructor |
| 41 |
|
* @return \Xyster\Type\Proxy\IProxy the proxy object created |
| 42 |
|
*/ |
| 43 |
|
public function create( array $args = null ) |
| 44 |
|
{ |
| 45 |
1 |
$type = $this->createType(); |
| 46 |
1 |
array_unshift($args, $this->_handler); |
| 47 |
1 |
return $type->getClass()->newInstanceArgs($args); |
| 48 |
|
} |
| 49 |
|
|
| 50 |
|
/** |
| 51 |
|
* Creates a proxy class |
| 52 |
|
* |
| 53 |
|
* @return \Xyster\Type\Type |
| 54 |
|
*/ |
| 55 |
|
public function createType() |
| 56 |
|
{ |
| 57 |
2 |
$key = ''; |
| 58 |
2 |
if ( self::usesCache() ) { |
| 59 |
2 |
if ( $this->_parent ) { |
| 60 |
2 |
$key .= $this->_parent->getName(); |
| 61 |
2 |
} |
| 62 |
2 |
$key .= '|'; |
| 63 |
2 |
foreach( $this->_interfaces as $iface ) { |
| 64 |
2 |
$key .= $iface->getName() . '|'; |
| 65 |
2 |
} |
| 66 |
2 |
if ( $this->_handler ) { |
| 67 |
2 |
$key .= spl_object_hash($this->_handler) . '|'; |
| 68 |
2 |
} |
| 69 |
2 |
$key = ( $this->_callConstructor ) ? 'ccy' : 'ccn'; |
| 70 |
2 |
if ( isset(self::$_types[$key]) ) { |
| 71 |
1 |
return self::$_types[$key]; |
| 72 |
|
} |
| 73 |
2 |
} |
| 74 |
2 |
eval($this->_getClass()); |
| 75 |
2 |
$type = new \Xyster\Type\Type($this->_className); |
| 76 |
2 |
if ( self::usesCache() ) { |
| 77 |
2 |
self::$_types[$key] = $type; |
| 78 |
2 |
} |
| 79 |
2 |
return $type; |
| 80 |
|
} |
| 81 |
|
|
| 82 |
|
/** |
| 83 |
|
* Sets whether the parent constructor is called (default is false) |
| 84 |
|
* |
| 85 |
|
* @param boolean $flag |
| 86 |
|
* @return \Xyster\Type\Proxy\Builder provides a fluent interface |
| 87 |
|
*/ |
| 88 |
|
public function setCallParentConstructor( $flag = true ) |
| 89 |
|
{ |
| 90 |
2 |
$this->_callConstructor = $flag; |
| 91 |
2 |
return $this; |
| 92 |
|
} |
| 93 |
|
|
| 94 |
|
/** |
| 95 |
|
* Sets the handler used for method callbacks |
| 96 |
|
* |
| 97 |
|
* @param \Xyster\Type\Proxy\IHandler $handler |
| 98 |
|
* @return \Xyster\Type\Proxy\Builder provides a fluent interface |
| 99 |
|
*/ |
| 100 |
|
public function setHandler( IHandler $handler ) |
| 101 |
|
{ |
| 102 |
3 |
$this->_handler = $handler; |
| 103 |
3 |
return $this; |
| 104 |
|
} |
| 105 |
|
|
| 106 |
|
/** |
| 107 |
|
* Sets any interfaces the generated class should implement |
| 108 |
|
* |
| 109 |
|
* @param array $interfaces An array of \Xyster\Type\Type objects |
| 110 |
|
* @return \Xyster\Type\Proxy\Builder provides a fluent interface |
| 111 |
|
* @throws ProxyException if any items in array aren't interfaces |
| 112 |
|
*/ |
| 113 |
|
public function setInterfaces( array $interfaces ) |
| 114 |
|
{ |
| 115 |
4 |
foreach( $interfaces as $interface ) { |
| 116 |
4 |
if ( !$interface instanceof Type || |
| 117 |
4 |
!$interface->isObject() || |
| 118 |
4 |
!$interface->getClass()->isInterface() ) { |
| 119 |
1 |
throw new ProxyException('Not an interface: ' . $interface); |
| 120 |
|
} |
| 121 |
3 |
$this->_interfaces[] = $interface; |
| 122 |
3 |
} |
| 123 |
3 |
return $this; |
| 124 |
|
} |
| 125 |
|
|
| 126 |
|
/** |
| 127 |
|
* Sets the parent type the generated class should extend |
| 128 |
|
* |
| 129 |
|
* @param Xyster\Type\Type $parent |
| 130 |
|
* @return \Xyster\Type\Proxy\Builder provides a fluent interface |
| 131 |
|
* @throws ProxyException if the type is invalid as a parent |
| 132 |
|
*/ |
| 133 |
|
public function setParent( Type $parent ) |
| 134 |
|
{ |
| 135 |
3 |
if ( !$parent->isObject() || $parent->getClass()->isInterface() || $parent->getClass()->isFinal() ) { |
| 136 |
|
throw new ProxyException('Parent type must be a non-final or abstract class'); |
| 137 |
|
} |
| 138 |
3 |
$this->_parent = $parent; |
| 139 |
3 |
return $this; |
| 140 |
|
} |
| 141 |
|
|
| 142 |
|
/** |
| 143 |
|
* Whether the builder caches the classes it generates |
| 144 |
|
* |
| 145 |
|
* @return boolean |
| 146 |
|
*/ |
| 147 |
|
public static function usesCache() |
| 148 |
|
{ |
| 149 |
3 |
return self::$_cache; |
| 150 |
|
} |
| 151 |
|
|
| 152 |
|
/** |
| 153 |
|
* Sets whether the builder caches the classes it generates (default true) |
| 154 |
|
* |
| 155 |
|
* @param boolean $flag |
| 156 |
|
*/ |
| 157 |
|
public static function setCache( $flag = true ) |
| 158 |
|
{ |
| 159 |
2 |
self::$_cache = $flag; |
| 160 |
|
} |
| 161 |
|
|
| 162 |
|
/** |
| 163 |
|
* Gets the full class source |
| 164 |
|
* |
| 165 |
|
* @return string |
| 166 |
|
*/ |
| 167 |
|
private function _getClass() |
| 168 |
|
{ |
| 169 |
2 |
$class = $this->_getClassHead() . |
| 170 |
2 |
" private \$_handler;\n" . |
| 171 |
2 |
" private \$_class;\n" . |
| 172 |
2 |
$this->_getConstructor() . |
| 173 |
2 |
" public function getHandler()\n { return \$this->_handler; }\n"; |
| 174 |
2 |
$methods = array(); |
| 175 |
2 |
foreach( $this->_parent->getClass()->getMethods() as $method ) { |
| 176 |
2 |
if ( !$method->isConstructor() && !$method->isFinal() ) { |
| 177 |
2 |
$methods[$method->getName()] = $this->_getMethodDefinitionFromExisting($method); |
| 178 |
2 |
} |
| 179 |
2 |
} |
| 180 |
2 |
foreach( $this->_interfaces as $interface ) { |
| 181 |
2 |
foreach( $interface->getClass()->getMethods() as $method ) { |
| 182 |
2 |
if ( !$method->isConstructor() ) { |
| 183 |
2 |
$methods[$method->getName()] = $this->_getMethodDefinitionFromExisting($method); |
| 184 |
2 |
} |
| 185 |
2 |
} |
| 186 |
2 |
} |
| 187 |
2 |
$class .= implode("\n", $methods) . "}\n"; |
| 188 |
2 |
return $class; |
| 189 |
|
} |
| 190 |
|
|
| 191 |
|
/** |
| 192 |
|
* Gets the string for the proxy head declaration |
| 193 |
|
* |
| 194 |
|
* @return string |
| 195 |
|
*/ |
| 196 |
|
private function _getClassHead() |
| 197 |
|
{ |
| 198 |
2 |
$def = 'class '; |
| 199 |
2 |
$extends = ''; |
| 200 |
2 |
$className = 'XysterTypeProxy_'; |
| 201 |
2 |
if ( $this->_parent ) { |
| 202 |
2 |
$className .= str_replace('\\', '_', $this->_parent->getName()) . '_'; |
| 203 |
2 |
$extends = ' extends ' . $this->_parent->getName(); |
| 204 |
2 |
} |
| 205 |
2 |
$className .= substr(md5(microtime()), 0, 8); |
| 206 |
2 |
$this->_className = $className; |
| 207 |
2 |
$def .= $className . $extends . ' implements \Xyster\Type\Proxy\IProxy'; |
| 208 |
2 |
foreach( $this->_interfaces as $iface ) { |
| 209 |
2 |
$def .= ', ' . $iface->getName(); |
| 210 |
2 |
} |
| 211 |
2 |
$def .= " {\n"; |
| 212 |
2 |
return $def; |
| 213 |
|
} |
| 214 |
|
|
| 215 |
|
/** |
| 216 |
|
* Gets the body of the constructor method |
| 217 |
|
* |
| 218 |
|
* @return string |
| 219 |
|
*/ |
| 220 |
|
private function _getConstructor() |
| 221 |
|
{ |
| 222 |
2 |
$def = ''; |
| 223 |
|
$body = "\$this->_handler = \$handler;\n" . |
| 224 |
2 |
"\$this->_class = new \ReflectionClass(__CLASS__);\n"; |
| 225 |
|
$decl = "%s function __construct(\Xyster\Type\Proxy\IHandler " . |
| 226 |
2 |
"\$handler%s) {\n"; |
| 227 |
|
|
| 228 |
2 |
if ( $this->_parent && $this->_parent->getClass()->getConstructor() ) { |
| 229 |
2 |
$constructor = $this->_parent->getClass()->getConstructor(); |
| 230 |
2 |
if ( !$constructor->isFinal() ) { |
| 231 |
2 |
$visibility = 'public'; |
| 232 |
2 |
if ($constructor->isPrivate()) { |
| 233 |
|
$visibility = 'private'; |
| 234 |
2 |
} else if ($constructor->isProtected()) { |
| 235 |
|
$visibility = 'protected'; |
| 236 |
|
} |
| 237 |
2 |
$params = ''; |
| 238 |
2 |
if ( $this->_callConstructor ) { |
| 239 |
1 |
$params = $this->_getMethodParameters($constructor); |
| 240 |
1 |
$paramNames = array(); |
| 241 |
1 |
if ( $params ) { |
| 242 |
1 |
$params = ', ' . $params; |
| 243 |
1 |
foreach( $constructor->getParameters() as $param ) { |
| 244 |
1 |
$paramNames[] = '$' . $param->getName(); |
| 245 |
1 |
} |
| 246 |
1 |
} |
| 247 |
1 |
$body .= "parent::__construct(" . implode(',', $paramNames) . ");\n"; |
| 248 |
1 |
} |
| 249 |
2 |
$def .= sprintf($decl, $visibility, $params); |
| 250 |
2 |
} |
| 251 |
2 |
} else { |
| 252 |
|
$def .= sprintf($decl, 'public', ''); |
| 253 |
|
} |
| 254 |
2 |
$def .= $body; |
| 255 |
2 |
$def .= "}\n"; |
| 256 |
2 |
return $def; |
| 257 |
|
} |
| 258 |
|
|
| 259 |
|
/** |
| 260 |
|
* Gets the method definition from a ReflectionMethod object |
| 261 |
|
* |
| 262 |
|
* @param ReflectionMethod $method |
| 263 |
|
* @return string |
| 264 |
|
*/ |
| 265 |
|
private function _getMethodDefinitionFromExisting(\ReflectionMethod $method) |
| 266 |
|
{ |
| 267 |
|
/* |
| 268 |
|
* This method body was taken from PHPUnit_Framework_MockObject_Mock, |
| 269 |
|
* specifically the 'generateMethodDefinitionFromExisting' method. |
| 270 |
|
* Used under compatible BSD license. |
| 271 |
|
* Copyright (c) 2002-2008, Sebastian Bergmann <sb@sebastian-bergmann.de> |
| 272 |
|
*/ |
| 273 |
2 |
if ($method->isPrivate()) { |
| 274 |
|
$modifier = 'private'; |
| 275 |
2 |
} else if ($method->isProtected()) { |
| 276 |
|
$modifier = 'protected'; |
| 277 |
|
} else { |
| 278 |
2 |
$modifier = 'public'; |
| 279 |
|
} |
| 280 |
|
|
| 281 |
2 |
if ($method->isStatic()) { |
| 282 |
|
$modifier .= ' static'; |
| 283 |
|
} |
| 284 |
|
|
| 285 |
2 |
if ($method->returnsReference()) { |
| 286 |
|
$reference = '&'; |
| 287 |
|
} else { |
| 288 |
2 |
$reference = ''; |
| 289 |
|
} |
| 290 |
|
|
| 291 |
2 |
return $this->_getMethodDefinition( |
| 292 |
2 |
$method->getDeclaringClass()->getName(), |
| 293 |
2 |
$method->getName(), |
| 294 |
2 |
$modifier, |
| 295 |
2 |
$reference, |
| 296 |
2 |
$this->_getMethodParameters($method) |
| 297 |
2 |
); |
| 298 |
|
} |
| 299 |
|
|
| 300 |
|
/** |
| 301 |
|
* Gets the actual method definition |
| 302 |
|
* |
| 303 |
|
* @param string $className |
| 304 |
|
* @param string $methodName |
| 305 |
|
* @param string $modifier |
| 306 |
|
* @param string $reference |
| 307 |
|
* @param string $parameters |
| 308 |
|
* @return string |
| 309 |
|
*/ |
| 310 |
|
private function _getMethodDefinition($className, $methodName, $modifier, $reference = '', $parameters = '') |
| 311 |
|
{ |
| 312 |
2 |
$parent = $this->_parent; |
| 313 |
2 |
$parentMethod = 'null'; |
| 314 |
2 |
if ( $parent ) { |
| 315 |
2 |
$class = $parent->getClass(); |
| 316 |
2 |
if ( $class->hasMethod($methodName) && |
| 317 |
2 |
!$class->getMethod($methodName)->isAbstract() ) { |
| 318 |
2 |
$parentMethod = 'new \ReflectionMethod("'.$class->getName().'", __FUNCTION__)'; |
| 319 |
2 |
} |
| 320 |
2 |
} |
| 321 |
2 |
$handlerSource = ''; |
| 322 |
2 |
if ( $this->_handler ) { |
| 323 |
|
$handlerSource = |
| 324 |
|
" return \$this->_handler->invoke(\$this,\n" . |
| 325 |
2 |
" \$this->_class->getMethod(__FUNCTION__),\n" . |
| 326 |
2 |
" \$args,\n" . |
| 327 |
2 |
" %s);\n"; |
| 328 |
2 |
} |
| 329 |
2 |
return sprintf( |
| 330 |
|
"\n %s function %s%s(%s) {\n" . |
| 331 |
2 |
" \$args = func_get_args();\n" . |
| 332 |
2 |
$handlerSource . |
| 333 |
2 |
" }\n", |
| 334 |
2 |
$modifier, |
| 335 |
2 |
$reference, |
| 336 |
2 |
$methodName, |
| 337 |
2 |
$parameters, |
| 338 |
|
$parentMethod |
| 339 |
2 |
); |
| 340 |
|
} |
| 341 |
|
|
| 342 |
|
/** |
| 343 |
|
* Gets the method parameter declaration |
| 344 |
|
* |
| 345 |
|
* @param ReflectionMethod $method |
| 346 |
|
* @return string |
| 347 |
|
*/ |
| 348 |
|
private function _getMethodParameters( \ReflectionMethod $method ) |
| 349 |
|
{ |
| 350 |
|
/* |
| 351 |
|
* This method body was taken from PHPUnit_Framework_MockObject_Mock, |
| 352 |
|
* specifically the 'generateMethodDefinition' method. |
| 353 |
|
* Used under compatible BSD license. |
| 354 |
|
* Copyright (c) 2002-2008, Sebastian Bergmann <sb@sebastian-bergmann.de> |
| 355 |
|
*/ |
| 356 |
2 |
$parameters = array(); |
| 357 |
|
|
| 358 |
2 |
foreach ($method->getParameters() as $parameter) { |
| 359 |
2 |
$name = '$' . $parameter->getName(); |
| 360 |
2 |
$typeHint = ''; |
| 361 |
|
|
| 362 |
2 |
if ($parameter->isArray()) { |
| 363 |
|
$typeHint = 'array '; |
| 364 |
|
} else { |
| 365 |
|
try { |
| 366 |
2 |
$class = $parameter->getClass(); |
| 367 |
|
} |
| 368 |
|
|
| 369 |
2 |
catch (ReflectionException $e) { |
| 370 |
|
$class = FALSE; |
| 371 |
|
} |
| 372 |
|
|
| 373 |
2 |
if ($class) { |
| 374 |
|
$typeHint = $class->getName() . ' '; |
| 375 |
|
} |
| 376 |
|
} |
| 377 |
|
|
| 378 |
2 |
$default = ''; |
| 379 |
|
|
| 380 |
2 |
if ($parameter->isDefaultValueAvailable()) { |
| 381 |
|
$value = $parameter->getDefaultValue(); |
| 382 |
|
$default = ' = ' . var_export($value, TRUE); |
| 383 |
|
} |
| 384 |
|
|
| 385 |
2 |
else if ($parameter->isOptional()) { |
| 386 |
|
$default = ' = null'; |
| 387 |
|
} |
| 388 |
|
|
| 389 |
2 |
$ref = ''; |
| 390 |
|
|
| 391 |
2 |
if ($parameter->isPassedByReference()) { |
| 392 |
|
$ref = '&'; |
| 393 |
|
} |
| 394 |
|
|
| 395 |
2 |
$parameters[] = $typeHint . $ref . $name . $default; |
| 396 |
2 |
} |
| 397 |
|
|
| 398 |
2 |
return implode(', ', $parameters); |
| 399 |
|
} |
| 400 |
1 |
} |