Mercurial > hg > isophonics-drupal-site
comparison vendor/doctrine/common/lib/Doctrine/Common/Proxy/ProxyGenerator.php @ 0:4c8ae668cc8c
Initial import (non-working)
| author | Chris Cannam |
|---|---|
| date | Wed, 29 Nov 2017 16:09:58 +0000 |
| parents | |
| children | 7a779792577d |
comparison
equal
deleted
inserted
replaced
| -1:000000000000 | 0:4c8ae668cc8c |
|---|---|
| 1 <?php | |
| 2 /* | |
| 3 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 4 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 5 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 6 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 7 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 8 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 9 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 10 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 11 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 12 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 13 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 14 * | |
| 15 * This software consists of voluntary contributions made by many individuals | |
| 16 * and is licensed under the MIT license. For more information, see | |
| 17 * <http://www.doctrine-project.org>. | |
| 18 */ | |
| 19 | |
| 20 namespace Doctrine\Common\Proxy; | |
| 21 | |
| 22 use Doctrine\Common\Persistence\Mapping\ClassMetadata; | |
| 23 use Doctrine\Common\Proxy\Exception\InvalidArgumentException; | |
| 24 use Doctrine\Common\Proxy\Exception\UnexpectedValueException; | |
| 25 use Doctrine\Common\Util\ClassUtils; | |
| 26 | |
| 27 /** | |
| 28 * This factory is used to generate proxy classes. | |
| 29 * It builds proxies from given parameters, a template and class metadata. | |
| 30 * | |
| 31 * @author Marco Pivetta <ocramius@gmail.com> | |
| 32 * @since 2.4 | |
| 33 */ | |
| 34 class ProxyGenerator | |
| 35 { | |
| 36 /** | |
| 37 * Used to match very simple id methods that don't need | |
| 38 * to be decorated since the identifier is known. | |
| 39 */ | |
| 40 const PATTERN_MATCH_ID_METHOD = '((public\s+)?(function\s+%s\s*\(\)\s*)\s*{\s*return\s*\$this->%s;\s*})i'; | |
| 41 | |
| 42 /** | |
| 43 * The namespace that contains all proxy classes. | |
| 44 * | |
| 45 * @var string | |
| 46 */ | |
| 47 private $proxyNamespace; | |
| 48 | |
| 49 /** | |
| 50 * The directory that contains all proxy classes. | |
| 51 * | |
| 52 * @var string | |
| 53 */ | |
| 54 private $proxyDirectory; | |
| 55 | |
| 56 /** | |
| 57 * Map of callables used to fill in placeholders set in the template. | |
| 58 * | |
| 59 * @var string[]|callable[] | |
| 60 */ | |
| 61 protected $placeholders = [ | |
| 62 'baseProxyInterface' => Proxy::class, | |
| 63 'additionalProperties' => '', | |
| 64 ]; | |
| 65 | |
| 66 /** | |
| 67 * Template used as a blueprint to generate proxies. | |
| 68 * | |
| 69 * @var string | |
| 70 */ | |
| 71 protected $proxyClassTemplate = '<?php | |
| 72 | |
| 73 namespace <namespace>; | |
| 74 | |
| 75 /** | |
| 76 * DO NOT EDIT THIS FILE - IT WAS CREATED BY DOCTRINE\'S PROXY GENERATOR | |
| 77 */ | |
| 78 class <proxyShortClassName> extends \<className> implements \<baseProxyInterface> | |
| 79 { | |
| 80 /** | |
| 81 * @var \Closure the callback responsible for loading properties in the proxy object. This callback is called with | |
| 82 * three parameters, being respectively the proxy object to be initialized, the method that triggered the | |
| 83 * initialization process and an array of ordered parameters that were passed to that method. | |
| 84 * | |
| 85 * @see \Doctrine\Common\Persistence\Proxy::__setInitializer | |
| 86 */ | |
| 87 public $__initializer__; | |
| 88 | |
| 89 /** | |
| 90 * @var \Closure the callback responsible of loading properties that need to be copied in the cloned object | |
| 91 * | |
| 92 * @see \Doctrine\Common\Persistence\Proxy::__setCloner | |
| 93 */ | |
| 94 public $__cloner__; | |
| 95 | |
| 96 /** | |
| 97 * @var boolean flag indicating if this object was already initialized | |
| 98 * | |
| 99 * @see \Doctrine\Common\Persistence\Proxy::__isInitialized | |
| 100 */ | |
| 101 public $__isInitialized__ = false; | |
| 102 | |
| 103 /** | |
| 104 * @var array properties to be lazy loaded, with keys being the property | |
| 105 * names and values being their default values | |
| 106 * | |
| 107 * @see \Doctrine\Common\Persistence\Proxy::__getLazyProperties | |
| 108 */ | |
| 109 public static $lazyPropertiesDefaults = [<lazyPropertiesDefaults>]; | |
| 110 | |
| 111 <additionalProperties> | |
| 112 | |
| 113 <constructorImpl> | |
| 114 | |
| 115 <magicGet> | |
| 116 | |
| 117 <magicSet> | |
| 118 | |
| 119 <magicIsset> | |
| 120 | |
| 121 <sleepImpl> | |
| 122 | |
| 123 <wakeupImpl> | |
| 124 | |
| 125 <cloneImpl> | |
| 126 | |
| 127 /** | |
| 128 * Forces initialization of the proxy | |
| 129 */ | |
| 130 public function __load() | |
| 131 { | |
| 132 $this->__initializer__ && $this->__initializer__->__invoke($this, \'__load\', []); | |
| 133 } | |
| 134 | |
| 135 /** | |
| 136 * {@inheritDoc} | |
| 137 * @internal generated method: use only when explicitly handling proxy specific loading logic | |
| 138 */ | |
| 139 public function __isInitialized() | |
| 140 { | |
| 141 return $this->__isInitialized__; | |
| 142 } | |
| 143 | |
| 144 /** | |
| 145 * {@inheritDoc} | |
| 146 * @internal generated method: use only when explicitly handling proxy specific loading logic | |
| 147 */ | |
| 148 public function __setInitialized($initialized) | |
| 149 { | |
| 150 $this->__isInitialized__ = $initialized; | |
| 151 } | |
| 152 | |
| 153 /** | |
| 154 * {@inheritDoc} | |
| 155 * @internal generated method: use only when explicitly handling proxy specific loading logic | |
| 156 */ | |
| 157 public function __setInitializer(\Closure $initializer = null) | |
| 158 { | |
| 159 $this->__initializer__ = $initializer; | |
| 160 } | |
| 161 | |
| 162 /** | |
| 163 * {@inheritDoc} | |
| 164 * @internal generated method: use only when explicitly handling proxy specific loading logic | |
| 165 */ | |
| 166 public function __getInitializer() | |
| 167 { | |
| 168 return $this->__initializer__; | |
| 169 } | |
| 170 | |
| 171 /** | |
| 172 * {@inheritDoc} | |
| 173 * @internal generated method: use only when explicitly handling proxy specific loading logic | |
| 174 */ | |
| 175 public function __setCloner(\Closure $cloner = null) | |
| 176 { | |
| 177 $this->__cloner__ = $cloner; | |
| 178 } | |
| 179 | |
| 180 /** | |
| 181 * {@inheritDoc} | |
| 182 * @internal generated method: use only when explicitly handling proxy specific cloning logic | |
| 183 */ | |
| 184 public function __getCloner() | |
| 185 { | |
| 186 return $this->__cloner__; | |
| 187 } | |
| 188 | |
| 189 /** | |
| 190 * {@inheritDoc} | |
| 191 * @internal generated method: use only when explicitly handling proxy specific loading logic | |
| 192 * @static | |
| 193 */ | |
| 194 public function __getLazyProperties() | |
| 195 { | |
| 196 return self::$lazyPropertiesDefaults; | |
| 197 } | |
| 198 | |
| 199 <methods> | |
| 200 } | |
| 201 '; | |
| 202 | |
| 203 /** | |
| 204 * Initializes a new instance of the <tt>ProxyFactory</tt> class that is | |
| 205 * connected to the given <tt>EntityManager</tt>. | |
| 206 * | |
| 207 * @param string $proxyDirectory The directory to use for the proxy classes. It must exist. | |
| 208 * @param string $proxyNamespace The namespace to use for the proxy classes. | |
| 209 * | |
| 210 * @throws InvalidArgumentException | |
| 211 */ | |
| 212 public function __construct($proxyDirectory, $proxyNamespace) | |
| 213 { | |
| 214 if ( ! $proxyDirectory) { | |
| 215 throw InvalidArgumentException::proxyDirectoryRequired(); | |
| 216 } | |
| 217 | |
| 218 if ( ! $proxyNamespace) { | |
| 219 throw InvalidArgumentException::proxyNamespaceRequired(); | |
| 220 } | |
| 221 | |
| 222 $this->proxyDirectory = $proxyDirectory; | |
| 223 $this->proxyNamespace = $proxyNamespace; | |
| 224 } | |
| 225 | |
| 226 /** | |
| 227 * Sets a placeholder to be replaced in the template. | |
| 228 * | |
| 229 * @param string $name | |
| 230 * @param string|callable $placeholder | |
| 231 * | |
| 232 * @throws InvalidArgumentException | |
| 233 */ | |
| 234 public function setPlaceholder($name, $placeholder) | |
| 235 { | |
| 236 if ( ! is_string($placeholder) && ! is_callable($placeholder)) { | |
| 237 throw InvalidArgumentException::invalidPlaceholder($name); | |
| 238 } | |
| 239 | |
| 240 $this->placeholders[$name] = $placeholder; | |
| 241 } | |
| 242 | |
| 243 /** | |
| 244 * Sets the base template used to create proxy classes. | |
| 245 * | |
| 246 * @param string $proxyClassTemplate | |
| 247 */ | |
| 248 public function setProxyClassTemplate($proxyClassTemplate) | |
| 249 { | |
| 250 $this->proxyClassTemplate = (string) $proxyClassTemplate; | |
| 251 } | |
| 252 | |
| 253 /** | |
| 254 * Generates a proxy class file. | |
| 255 * | |
| 256 * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class Metadata for the original class. | |
| 257 * @param string|bool $fileName Filename (full path) for the generated class. If none is given, eval() is used. | |
| 258 * | |
| 259 * @throws UnexpectedValueException | |
| 260 */ | |
| 261 public function generateProxyClass(ClassMetadata $class, $fileName = false) | |
| 262 { | |
| 263 preg_match_all('(<([a-zA-Z]+)>)', $this->proxyClassTemplate, $placeholderMatches); | |
| 264 | |
| 265 $placeholderMatches = array_combine($placeholderMatches[0], $placeholderMatches[1]); | |
| 266 $placeholders = []; | |
| 267 | |
| 268 foreach ($placeholderMatches as $placeholder => $name) { | |
| 269 $placeholders[$placeholder] = isset($this->placeholders[$name]) | |
| 270 ? $this->placeholders[$name] | |
| 271 : [$this, 'generate' . $name]; | |
| 272 } | |
| 273 | |
| 274 foreach ($placeholders as & $placeholder) { | |
| 275 if (is_callable($placeholder)) { | |
| 276 $placeholder = call_user_func($placeholder, $class); | |
| 277 } | |
| 278 } | |
| 279 | |
| 280 $proxyCode = strtr($this->proxyClassTemplate, $placeholders); | |
| 281 | |
| 282 if ( ! $fileName) { | |
| 283 $proxyClassName = $this->generateNamespace($class) . '\\' . $this->generateProxyShortClassName($class); | |
| 284 | |
| 285 if ( ! class_exists($proxyClassName)) { | |
| 286 eval(substr($proxyCode, 5)); | |
| 287 } | |
| 288 | |
| 289 return; | |
| 290 } | |
| 291 | |
| 292 $parentDirectory = dirname($fileName); | |
| 293 | |
| 294 if ( ! is_dir($parentDirectory) && (false === @mkdir($parentDirectory, 0775, true))) { | |
| 295 throw UnexpectedValueException::proxyDirectoryNotWritable($this->proxyDirectory); | |
| 296 } | |
| 297 | |
| 298 if ( ! is_writable($parentDirectory)) { | |
| 299 throw UnexpectedValueException::proxyDirectoryNotWritable($this->proxyDirectory); | |
| 300 } | |
| 301 | |
| 302 $tmpFileName = $fileName . '.' . uniqid('', true); | |
| 303 | |
| 304 file_put_contents($tmpFileName, $proxyCode); | |
| 305 @chmod($tmpFileName, 0664); | |
| 306 rename($tmpFileName, $fileName); | |
| 307 } | |
| 308 | |
| 309 /** | |
| 310 * Generates the proxy short class name to be used in the template. | |
| 311 * | |
| 312 * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class | |
| 313 * | |
| 314 * @return string | |
| 315 */ | |
| 316 private function generateProxyShortClassName(ClassMetadata $class) | |
| 317 { | |
| 318 $proxyClassName = ClassUtils::generateProxyClassName($class->getName(), $this->proxyNamespace); | |
| 319 $parts = explode('\\', strrev($proxyClassName), 2); | |
| 320 | |
| 321 return strrev($parts[0]); | |
| 322 } | |
| 323 | |
| 324 /** | |
| 325 * Generates the proxy namespace. | |
| 326 * | |
| 327 * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class | |
| 328 * | |
| 329 * @return string | |
| 330 */ | |
| 331 private function generateNamespace(ClassMetadata $class) | |
| 332 { | |
| 333 $proxyClassName = ClassUtils::generateProxyClassName($class->getName(), $this->proxyNamespace); | |
| 334 $parts = explode('\\', strrev($proxyClassName), 2); | |
| 335 | |
| 336 return strrev($parts[1]); | |
| 337 } | |
| 338 | |
| 339 /** | |
| 340 * Generates the original class name. | |
| 341 * | |
| 342 * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class | |
| 343 * | |
| 344 * @return string | |
| 345 */ | |
| 346 private function generateClassName(ClassMetadata $class) | |
| 347 { | |
| 348 return ltrim($class->getName(), '\\'); | |
| 349 } | |
| 350 | |
| 351 /** | |
| 352 * Generates the array representation of lazy loaded public properties and their default values. | |
| 353 * | |
| 354 * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class | |
| 355 * | |
| 356 * @return string | |
| 357 */ | |
| 358 private function generateLazyPropertiesDefaults(ClassMetadata $class) | |
| 359 { | |
| 360 $lazyPublicProperties = $this->getLazyLoadedPublicProperties($class); | |
| 361 $values = []; | |
| 362 | |
| 363 foreach ($lazyPublicProperties as $key => $value) { | |
| 364 $values[] = var_export($key, true) . ' => ' . var_export($value, true); | |
| 365 } | |
| 366 | |
| 367 return implode(', ', $values); | |
| 368 } | |
| 369 | |
| 370 /** | |
| 371 * Generates the constructor code (un-setting public lazy loaded properties, setting identifier field values). | |
| 372 * | |
| 373 * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class | |
| 374 * | |
| 375 * @return string | |
| 376 */ | |
| 377 private function generateConstructorImpl(ClassMetadata $class) | |
| 378 { | |
| 379 $constructorImpl = <<<'EOT' | |
| 380 /** | |
| 381 * @param \Closure $initializer | |
| 382 * @param \Closure $cloner | |
| 383 */ | |
| 384 public function __construct($initializer = null, $cloner = null) | |
| 385 { | |
| 386 | |
| 387 EOT; | |
| 388 $toUnset = []; | |
| 389 | |
| 390 foreach ($this->getLazyLoadedPublicProperties($class) as $lazyPublicProperty => $unused) { | |
| 391 $toUnset[] = '$this->' . $lazyPublicProperty; | |
| 392 } | |
| 393 | |
| 394 $constructorImpl .= (empty($toUnset) ? '' : ' unset(' . implode(', ', $toUnset) . ");\n") | |
| 395 . <<<'EOT' | |
| 396 | |
| 397 $this->__initializer__ = $initializer; | |
| 398 $this->__cloner__ = $cloner; | |
| 399 } | |
| 400 EOT; | |
| 401 | |
| 402 return $constructorImpl; | |
| 403 } | |
| 404 | |
| 405 /** | |
| 406 * Generates the magic getter invoked when lazy loaded public properties are requested. | |
| 407 * | |
| 408 * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class | |
| 409 * | |
| 410 * @return string | |
| 411 */ | |
| 412 private function generateMagicGet(ClassMetadata $class) | |
| 413 { | |
| 414 $lazyPublicProperties = array_keys($this->getLazyLoadedPublicProperties($class)); | |
| 415 $reflectionClass = $class->getReflectionClass(); | |
| 416 $hasParentGet = false; | |
| 417 $returnReference = ''; | |
| 418 $inheritDoc = ''; | |
| 419 | |
| 420 if ($reflectionClass->hasMethod('__get')) { | |
| 421 $hasParentGet = true; | |
| 422 $inheritDoc = '{@inheritDoc}'; | |
| 423 | |
| 424 if ($reflectionClass->getMethod('__get')->returnsReference()) { | |
| 425 $returnReference = '& '; | |
| 426 } | |
| 427 } | |
| 428 | |
| 429 if (empty($lazyPublicProperties) && ! $hasParentGet) { | |
| 430 return ''; | |
| 431 } | |
| 432 | |
| 433 $magicGet = <<<EOT | |
| 434 /** | |
| 435 * $inheritDoc | |
| 436 * @param string \$name | |
| 437 */ | |
| 438 public function {$returnReference}__get(\$name) | |
| 439 { | |
| 440 | |
| 441 EOT; | |
| 442 | |
| 443 if ( ! empty($lazyPublicProperties)) { | |
| 444 $magicGet .= <<<'EOT' | |
| 445 if (array_key_exists($name, $this->__getLazyProperties())) { | |
| 446 $this->__initializer__ && $this->__initializer__->__invoke($this, '__get', [$name]); | |
| 447 | |
| 448 return $this->$name; | |
| 449 } | |
| 450 | |
| 451 | |
| 452 EOT; | |
| 453 } | |
| 454 | |
| 455 if ($hasParentGet) { | |
| 456 $magicGet .= <<<'EOT' | |
| 457 $this->__initializer__ && $this->__initializer__->__invoke($this, '__get', [$name]); | |
| 458 | |
| 459 return parent::__get($name); | |
| 460 | |
| 461 EOT; | |
| 462 } else { | |
| 463 $magicGet .= <<<'EOT' | |
| 464 trigger_error(sprintf('Undefined property: %s::$%s', __CLASS__, $name), E_USER_NOTICE); | |
| 465 | |
| 466 EOT; | |
| 467 } | |
| 468 | |
| 469 $magicGet .= " }"; | |
| 470 | |
| 471 return $magicGet; | |
| 472 } | |
| 473 | |
| 474 /** | |
| 475 * Generates the magic setter (currently unused). | |
| 476 * | |
| 477 * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class | |
| 478 * | |
| 479 * @return string | |
| 480 */ | |
| 481 private function generateMagicSet(ClassMetadata $class) | |
| 482 { | |
| 483 $lazyPublicProperties = $this->getLazyLoadedPublicProperties($class); | |
| 484 $hasParentSet = $class->getReflectionClass()->hasMethod('__set'); | |
| 485 | |
| 486 if (empty($lazyPublicProperties) && ! $hasParentSet) { | |
| 487 return ''; | |
| 488 } | |
| 489 | |
| 490 $inheritDoc = $hasParentSet ? '{@inheritDoc}' : ''; | |
| 491 $magicSet = <<<EOT | |
| 492 /** | |
| 493 * $inheritDoc | |
| 494 * @param string \$name | |
| 495 * @param mixed \$value | |
| 496 */ | |
| 497 public function __set(\$name, \$value) | |
| 498 { | |
| 499 | |
| 500 EOT; | |
| 501 | |
| 502 if ( ! empty($lazyPublicProperties)) { | |
| 503 $magicSet .= <<<'EOT' | |
| 504 if (array_key_exists($name, $this->__getLazyProperties())) { | |
| 505 $this->__initializer__ && $this->__initializer__->__invoke($this, '__set', [$name, $value]); | |
| 506 | |
| 507 $this->$name = $value; | |
| 508 | |
| 509 return; | |
| 510 } | |
| 511 | |
| 512 | |
| 513 EOT; | |
| 514 } | |
| 515 | |
| 516 if ($hasParentSet) { | |
| 517 $magicSet .= <<<'EOT' | |
| 518 $this->__initializer__ && $this->__initializer__->__invoke($this, '__set', [$name, $value]); | |
| 519 | |
| 520 return parent::__set($name, $value); | |
| 521 EOT; | |
| 522 } else { | |
| 523 $magicSet .= " \$this->\$name = \$value;"; | |
| 524 } | |
| 525 | |
| 526 $magicSet .= "\n }"; | |
| 527 | |
| 528 return $magicSet; | |
| 529 } | |
| 530 | |
| 531 /** | |
| 532 * Generates the magic issetter invoked when lazy loaded public properties are checked against isset(). | |
| 533 * | |
| 534 * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class | |
| 535 * | |
| 536 * @return string | |
| 537 */ | |
| 538 private function generateMagicIsset(ClassMetadata $class) | |
| 539 { | |
| 540 $lazyPublicProperties = array_keys($this->getLazyLoadedPublicProperties($class)); | |
| 541 $hasParentIsset = $class->getReflectionClass()->hasMethod('__isset'); | |
| 542 | |
| 543 if (empty($lazyPublicProperties) && ! $hasParentIsset) { | |
| 544 return ''; | |
| 545 } | |
| 546 | |
| 547 $inheritDoc = $hasParentIsset ? '{@inheritDoc}' : ''; | |
| 548 $magicIsset = <<<EOT | |
| 549 /** | |
| 550 * $inheritDoc | |
| 551 * @param string \$name | |
| 552 * @return boolean | |
| 553 */ | |
| 554 public function __isset(\$name) | |
| 555 { | |
| 556 | |
| 557 EOT; | |
| 558 | |
| 559 if ( ! empty($lazyPublicProperties)) { | |
| 560 $magicIsset .= <<<'EOT' | |
| 561 if (array_key_exists($name, $this->__getLazyProperties())) { | |
| 562 $this->__initializer__ && $this->__initializer__->__invoke($this, '__isset', [$name]); | |
| 563 | |
| 564 return isset($this->$name); | |
| 565 } | |
| 566 | |
| 567 | |
| 568 EOT; | |
| 569 } | |
| 570 | |
| 571 if ($hasParentIsset) { | |
| 572 $magicIsset .= <<<'EOT' | |
| 573 $this->__initializer__ && $this->__initializer__->__invoke($this, '__isset', [$name]); | |
| 574 | |
| 575 return parent::__isset($name); | |
| 576 | |
| 577 EOT; | |
| 578 } else { | |
| 579 $magicIsset .= " return false;"; | |
| 580 } | |
| 581 | |
| 582 return $magicIsset . "\n }"; | |
| 583 } | |
| 584 | |
| 585 /** | |
| 586 * Generates implementation for the `__sleep` method of proxies. | |
| 587 * | |
| 588 * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class | |
| 589 * | |
| 590 * @return string | |
| 591 */ | |
| 592 private function generateSleepImpl(ClassMetadata $class) | |
| 593 { | |
| 594 $hasParentSleep = $class->getReflectionClass()->hasMethod('__sleep'); | |
| 595 $inheritDoc = $hasParentSleep ? '{@inheritDoc}' : ''; | |
| 596 $sleepImpl = <<<EOT | |
| 597 /** | |
| 598 * $inheritDoc | |
| 599 * @return array | |
| 600 */ | |
| 601 public function __sleep() | |
| 602 { | |
| 603 | |
| 604 EOT; | |
| 605 | |
| 606 if ($hasParentSleep) { | |
| 607 return $sleepImpl . <<<'EOT' | |
| 608 $properties = array_merge(['__isInitialized__'], parent::__sleep()); | |
| 609 | |
| 610 if ($this->__isInitialized__) { | |
| 611 $properties = array_diff($properties, array_keys($this->__getLazyProperties())); | |
| 612 } | |
| 613 | |
| 614 return $properties; | |
| 615 } | |
| 616 EOT; | |
| 617 } | |
| 618 | |
| 619 $allProperties = ['__isInitialized__']; | |
| 620 | |
| 621 /* @var $prop \ReflectionProperty */ | |
| 622 foreach ($class->getReflectionClass()->getProperties() as $prop) { | |
| 623 if ($prop->isStatic()) { | |
| 624 continue; | |
| 625 } | |
| 626 | |
| 627 $allProperties[] = $prop->isPrivate() | |
| 628 ? "\0" . $prop->getDeclaringClass()->getName() . "\0" . $prop->getName() | |
| 629 : $prop->getName(); | |
| 630 } | |
| 631 | |
| 632 $lazyPublicProperties = array_keys($this->getLazyLoadedPublicProperties($class)); | |
| 633 $protectedProperties = array_diff($allProperties, $lazyPublicProperties); | |
| 634 | |
| 635 foreach ($allProperties as &$property) { | |
| 636 $property = var_export($property, true); | |
| 637 } | |
| 638 | |
| 639 foreach ($protectedProperties as &$property) { | |
| 640 $property = var_export($property, true); | |
| 641 } | |
| 642 | |
| 643 $allProperties = implode(', ', $allProperties); | |
| 644 $protectedProperties = implode(', ', $protectedProperties); | |
| 645 | |
| 646 return $sleepImpl . <<<EOT | |
| 647 if (\$this->__isInitialized__) { | |
| 648 return [$allProperties]; | |
| 649 } | |
| 650 | |
| 651 return [$protectedProperties]; | |
| 652 } | |
| 653 EOT; | |
| 654 } | |
| 655 | |
| 656 /** | |
| 657 * Generates implementation for the `__wakeup` method of proxies. | |
| 658 * | |
| 659 * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class | |
| 660 * | |
| 661 * @return string | |
| 662 */ | |
| 663 private function generateWakeupImpl(ClassMetadata $class) | |
| 664 { | |
| 665 $unsetPublicProperties = []; | |
| 666 $hasWakeup = $class->getReflectionClass()->hasMethod('__wakeup'); | |
| 667 | |
| 668 foreach (array_keys($this->getLazyLoadedPublicProperties($class)) as $lazyPublicProperty) { | |
| 669 $unsetPublicProperties[] = '$this->' . $lazyPublicProperty; | |
| 670 } | |
| 671 | |
| 672 $shortName = $this->generateProxyShortClassName($class); | |
| 673 $inheritDoc = $hasWakeup ? '{@inheritDoc}' : ''; | |
| 674 $wakeupImpl = <<<EOT | |
| 675 /** | |
| 676 * $inheritDoc | |
| 677 */ | |
| 678 public function __wakeup() | |
| 679 { | |
| 680 if ( ! \$this->__isInitialized__) { | |
| 681 \$this->__initializer__ = function ($shortName \$proxy) { | |
| 682 \$proxy->__setInitializer(null); | |
| 683 \$proxy->__setCloner(null); | |
| 684 | |
| 685 \$existingProperties = get_object_vars(\$proxy); | |
| 686 | |
| 687 foreach (\$proxy->__getLazyProperties() as \$property => \$defaultValue) { | |
| 688 if ( ! array_key_exists(\$property, \$existingProperties)) { | |
| 689 \$proxy->\$property = \$defaultValue; | |
| 690 } | |
| 691 } | |
| 692 }; | |
| 693 | |
| 694 EOT; | |
| 695 | |
| 696 if ( ! empty($unsetPublicProperties)) { | |
| 697 $wakeupImpl .= "\n unset(" . implode(', ', $unsetPublicProperties) . ");"; | |
| 698 } | |
| 699 | |
| 700 $wakeupImpl .= "\n }"; | |
| 701 | |
| 702 if ($hasWakeup) { | |
| 703 $wakeupImpl .= "\n parent::__wakeup();"; | |
| 704 } | |
| 705 | |
| 706 $wakeupImpl .= "\n }"; | |
| 707 | |
| 708 return $wakeupImpl; | |
| 709 } | |
| 710 | |
| 711 /** | |
| 712 * Generates implementation for the `__clone` method of proxies. | |
| 713 * | |
| 714 * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class | |
| 715 * | |
| 716 * @return string | |
| 717 */ | |
| 718 private function generateCloneImpl(ClassMetadata $class) | |
| 719 { | |
| 720 $hasParentClone = $class->getReflectionClass()->hasMethod('__clone'); | |
| 721 $inheritDoc = $hasParentClone ? '{@inheritDoc}' : ''; | |
| 722 $callParentClone = $hasParentClone ? "\n parent::__clone();\n" : ''; | |
| 723 | |
| 724 return <<<EOT | |
| 725 /** | |
| 726 * $inheritDoc | |
| 727 */ | |
| 728 public function __clone() | |
| 729 { | |
| 730 \$this->__cloner__ && \$this->__cloner__->__invoke(\$this, '__clone', []); | |
| 731 $callParentClone } | |
| 732 EOT; | |
| 733 } | |
| 734 | |
| 735 /** | |
| 736 * Generates decorated methods by picking those available in the parent class. | |
| 737 * | |
| 738 * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class | |
| 739 * | |
| 740 * @return string | |
| 741 */ | |
| 742 private function generateMethods(ClassMetadata $class) | |
| 743 { | |
| 744 $methods = ''; | |
| 745 $methodNames = []; | |
| 746 $reflectionMethods = $class->getReflectionClass()->getMethods(\ReflectionMethod::IS_PUBLIC); | |
| 747 $skippedMethods = [ | |
| 748 '__sleep' => true, | |
| 749 '__clone' => true, | |
| 750 '__wakeup' => true, | |
| 751 '__get' => true, | |
| 752 '__set' => true, | |
| 753 '__isset' => true, | |
| 754 ]; | |
| 755 | |
| 756 foreach ($reflectionMethods as $method) { | |
| 757 $name = $method->getName(); | |
| 758 | |
| 759 if ( | |
| 760 $method->isConstructor() || | |
| 761 isset($skippedMethods[strtolower($name)]) || | |
| 762 isset($methodNames[$name]) || | |
| 763 $method->isFinal() || | |
| 764 $method->isStatic() || | |
| 765 ( ! $method->isPublic()) | |
| 766 ) { | |
| 767 continue; | |
| 768 } | |
| 769 | |
| 770 $methodNames[$name] = true; | |
| 771 $methods .= "\n /**\n" | |
| 772 . " * {@inheritDoc}\n" | |
| 773 . " */\n" | |
| 774 . ' public function '; | |
| 775 | |
| 776 if ($method->returnsReference()) { | |
| 777 $methods .= '&'; | |
| 778 } | |
| 779 | |
| 780 $methods .= $name . '(' . $this->buildParametersString($class, $method, $method->getParameters()) . ')'; | |
| 781 $methods .= $this->getMethodReturnType($method); | |
| 782 $methods .= "\n" . ' {' . "\n"; | |
| 783 | |
| 784 if ($this->isShortIdentifierGetter($method, $class)) { | |
| 785 $identifier = lcfirst(substr($name, 3)); | |
| 786 $fieldType = $class->getTypeOfField($identifier); | |
| 787 $cast = in_array($fieldType, ['integer', 'smallint']) ? '(int) ' : ''; | |
| 788 | |
| 789 $methods .= ' if ($this->__isInitialized__ === false) {' . "\n"; | |
| 790 $methods .= ' return ' . $cast . ' parent::' . $method->getName() . "();\n"; | |
| 791 $methods .= ' }' . "\n\n"; | |
| 792 } | |
| 793 | |
| 794 $invokeParamsString = implode(', ', $this->getParameterNamesForInvoke($method->getParameters())); | |
| 795 $callParamsString = implode(', ', $this->getParameterNamesForParentCall($method->getParameters())); | |
| 796 | |
| 797 $methods .= "\n \$this->__initializer__ " | |
| 798 . "&& \$this->__initializer__->__invoke(\$this, " . var_export($name, true) | |
| 799 . ", [" . $invokeParamsString . "]);" | |
| 800 . "\n\n return parent::" . $name . '(' . $callParamsString . ');' | |
| 801 . "\n" . ' }' . "\n"; | |
| 802 } | |
| 803 | |
| 804 return $methods; | |
| 805 } | |
| 806 | |
| 807 /** | |
| 808 * Generates the Proxy file name. | |
| 809 * | |
| 810 * @param string $className | |
| 811 * @param string $baseDirectory Optional base directory for proxy file name generation. | |
| 812 * If not specified, the directory configured on the Configuration of the | |
| 813 * EntityManager will be used by this factory. | |
| 814 * | |
| 815 * @return string | |
| 816 */ | |
| 817 public function getProxyFileName($className, $baseDirectory = null) | |
| 818 { | |
| 819 $baseDirectory = $baseDirectory ?: $this->proxyDirectory; | |
| 820 | |
| 821 return rtrim($baseDirectory, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . Proxy::MARKER | |
| 822 . str_replace('\\', '', $className) . '.php'; | |
| 823 } | |
| 824 | |
| 825 /** | |
| 826 * Checks if the method is a short identifier getter. | |
| 827 * | |
| 828 * What does this mean? For proxy objects the identifier is already known, | |
| 829 * however accessing the getter for this identifier usually triggers the | |
| 830 * lazy loading, leading to a query that may not be necessary if only the | |
| 831 * ID is interesting for the userland code (for example in views that | |
| 832 * generate links to the entity, but do not display anything else). | |
| 833 * | |
| 834 * @param \ReflectionMethod $method | |
| 835 * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class | |
| 836 * | |
| 837 * @return boolean | |
| 838 */ | |
| 839 private function isShortIdentifierGetter($method, ClassMetadata $class) | |
| 840 { | |
| 841 $identifier = lcfirst(substr($method->getName(), 3)); | |
| 842 $startLine = $method->getStartLine(); | |
| 843 $endLine = $method->getEndLine(); | |
| 844 $cheapCheck = ( | |
| 845 $method->getNumberOfParameters() == 0 | |
| 846 && substr($method->getName(), 0, 3) == 'get' | |
| 847 && in_array($identifier, $class->getIdentifier(), true) | |
| 848 && $class->hasField($identifier) | |
| 849 && (($endLine - $startLine) <= 4) | |
| 850 ); | |
| 851 | |
| 852 if ($cheapCheck) { | |
| 853 $code = file($method->getDeclaringClass()->getFileName()); | |
| 854 $code = trim(implode(' ', array_slice($code, $startLine - 1, $endLine - $startLine + 1))); | |
| 855 | |
| 856 $pattern = sprintf(self::PATTERN_MATCH_ID_METHOD, $method->getName(), $identifier); | |
| 857 | |
| 858 if (preg_match($pattern, $code)) { | |
| 859 return true; | |
| 860 } | |
| 861 } | |
| 862 | |
| 863 return false; | |
| 864 } | |
| 865 | |
| 866 /** | |
| 867 * Generates the list of public properties to be lazy loaded, with their default values. | |
| 868 * | |
| 869 * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class | |
| 870 * | |
| 871 * @return mixed[] | |
| 872 */ | |
| 873 private function getLazyLoadedPublicProperties(ClassMetadata $class) | |
| 874 { | |
| 875 $defaultProperties = $class->getReflectionClass()->getDefaultProperties(); | |
| 876 $properties = []; | |
| 877 | |
| 878 foreach ($class->getReflectionClass()->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) { | |
| 879 $name = $property->getName(); | |
| 880 | |
| 881 if (($class->hasField($name) || $class->hasAssociation($name)) && ! $class->isIdentifier($name)) { | |
| 882 $properties[$name] = $defaultProperties[$name]; | |
| 883 } | |
| 884 } | |
| 885 | |
| 886 return $properties; | |
| 887 } | |
| 888 | |
| 889 /** | |
| 890 * @param ClassMetadata $class | |
| 891 * @param \ReflectionMethod $method | |
| 892 * @param \ReflectionParameter[] $parameters | |
| 893 * | |
| 894 * @return string | |
| 895 */ | |
| 896 private function buildParametersString(ClassMetadata $class, \ReflectionMethod $method, array $parameters) | |
| 897 { | |
| 898 $parameterDefinitions = []; | |
| 899 | |
| 900 /* @var $param \ReflectionParameter */ | |
| 901 foreach ($parameters as $param) { | |
| 902 $parameterDefinition = ''; | |
| 903 | |
| 904 if ($parameterType = $this->getParameterType($class, $method, $param)) { | |
| 905 $parameterDefinition .= $parameterType . ' '; | |
| 906 } | |
| 907 | |
| 908 if ($param->isPassedByReference()) { | |
| 909 $parameterDefinition .= '&'; | |
| 910 } | |
| 911 | |
| 912 if (method_exists($param, 'isVariadic') && $param->isVariadic()) { | |
| 913 $parameterDefinition .= '...'; | |
| 914 } | |
| 915 | |
| 916 $parameters[] = '$' . $param->getName(); | |
| 917 $parameterDefinition .= '$' . $param->getName(); | |
| 918 | |
| 919 if ($param->isDefaultValueAvailable()) { | |
| 920 $parameterDefinition .= ' = ' . var_export($param->getDefaultValue(), true); | |
| 921 } | |
| 922 | |
| 923 $parameterDefinitions[] = $parameterDefinition; | |
| 924 } | |
| 925 | |
| 926 return implode(', ', $parameterDefinitions); | |
| 927 } | |
| 928 | |
| 929 /** | |
| 930 * @param ClassMetadata $class | |
| 931 * @param \ReflectionMethod $method | |
| 932 * @param \ReflectionParameter $parameter | |
| 933 * | |
| 934 * @return string|null | |
| 935 */ | |
| 936 private function getParameterType(ClassMetadata $class, \ReflectionMethod $method, \ReflectionParameter $parameter) | |
| 937 { | |
| 938 | |
| 939 // We need to pick the type hint class too | |
| 940 if ($parameter->isArray()) { | |
| 941 return 'array'; | |
| 942 } | |
| 943 | |
| 944 if ($parameter->isCallable()) { | |
| 945 return 'callable'; | |
| 946 } | |
| 947 | |
| 948 if (method_exists($parameter, 'hasType') && $parameter->hasType() && $parameter->getType()->isBuiltin()) { | |
| 949 return (string) $parameter->getType(); | |
| 950 } | |
| 951 | |
| 952 try { | |
| 953 $parameterClass = $parameter->getClass(); | |
| 954 | |
| 955 if ($parameterClass) { | |
| 956 return '\\' . $parameterClass->getName(); | |
| 957 } | |
| 958 } catch (\ReflectionException $previous) { | |
| 959 throw UnexpectedValueException::invalidParameterTypeHint( | |
| 960 $class->getName(), | |
| 961 $method->getName(), | |
| 962 $parameter->getName(), | |
| 963 $previous | |
| 964 ); | |
| 965 } | |
| 966 | |
| 967 return null; | |
| 968 } | |
| 969 | |
| 970 /** | |
| 971 * @param \ReflectionParameter[] $parameters | |
| 972 * | |
| 973 * @return string[] | |
| 974 */ | |
| 975 private function getParameterNamesForInvoke(array $parameters) | |
| 976 { | |
| 977 return array_map( | |
| 978 function (\ReflectionParameter $parameter) { | |
| 979 return '$' . $parameter->getName(); | |
| 980 }, | |
| 981 $parameters | |
| 982 ); | |
| 983 } | |
| 984 | |
| 985 /** | |
| 986 * @param \ReflectionParameter[] $parameters | |
| 987 * | |
| 988 * @return string[] | |
| 989 */ | |
| 990 private function getParameterNamesForParentCall(array $parameters) | |
| 991 { | |
| 992 return array_map( | |
| 993 function (\ReflectionParameter $parameter) { | |
| 994 $name = ''; | |
| 995 | |
| 996 if (method_exists($parameter, 'isVariadic') && $parameter->isVariadic()) { | |
| 997 $name .= '...'; | |
| 998 } | |
| 999 | |
| 1000 $name .= '$' . $parameter->getName(); | |
| 1001 | |
| 1002 return $name; | |
| 1003 }, | |
| 1004 $parameters | |
| 1005 ); | |
| 1006 } | |
| 1007 | |
| 1008 /** | |
| 1009 * @Param \ReflectionMethod $method | |
| 1010 * | |
| 1011 * @return string | |
| 1012 */ | |
| 1013 private function getMethodReturnType(\ReflectionMethod $method) | |
| 1014 { | |
| 1015 if (! (method_exists($method, 'hasReturnType') && $method->hasReturnType())) { | |
| 1016 return ''; | |
| 1017 } | |
| 1018 | |
| 1019 $returnType = $method->getReturnType(); | |
| 1020 | |
| 1021 if ($returnType->isBuiltin()) { | |
| 1022 return ': ' . $returnType; | |
| 1023 } | |
| 1024 | |
| 1025 $nameLower = strtolower((string) $returnType); | |
| 1026 | |
| 1027 if ('self' === $nameLower) { | |
| 1028 return ': \\' . $method->getDeclaringClass()->getName(); | |
| 1029 } | |
| 1030 | |
| 1031 if ('parent' === $nameLower) { | |
| 1032 return ': \\' . $method->getDeclaringClass()->getParentClass()->getName(); | |
| 1033 } | |
| 1034 | |
| 1035 return ': \\' . (string) $returnType; | |
| 1036 } | |
| 1037 } |
