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 20namespace Doctrine\Common\Proxy; 21 22use Doctrine\Common\Persistence\Mapping\ClassMetadata; 23use Doctrine\Common\Proxy\Exception\InvalidArgumentException; 24use Doctrine\Common\Proxy\Exception\UnexpectedValueException; 25use 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 */ 34class 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 = array( 62 'baseProxyInterface' => 'Doctrine\Common\Proxy\Proxy', 63 'additionalProperties' => '', 64 ); 65 66 /** 67 * Template used as a blueprint to generate proxies. 68 * 69 * @var string 70 */ 71 protected $proxyClassTemplate = '<?php 72 73namespace <namespace>; 74 75/** 76 * DO NOT EDIT THIS FILE - IT WAS CREATED BY DOCTRINE\'S PROXY GENERATOR 77 */ 78class <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 = array(<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\', array()); 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 = array(); 267 268 foreach ($placeholderMatches as $placeholder => $name) { 269 $placeholders[$placeholder] = isset($this->placeholders[$name]) 270 ? $this->placeholders[$name] 271 : array($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 = array(); 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 387EOT; 388 $toUnset = array(); 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 } 400EOT; 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 441EOT; 442 443 if ( ! empty($lazyPublicProperties)) { 444 $magicGet .= <<<'EOT' 445 if (array_key_exists($name, $this->__getLazyProperties())) { 446 $this->__initializer__ && $this->__initializer__->__invoke($this, '__get', array($name)); 447 448 return $this->$name; 449 } 450 451 452EOT; 453 } 454 455 if ($hasParentGet) { 456 $magicGet .= <<<'EOT' 457 $this->__initializer__ && $this->__initializer__->__invoke($this, '__get', array($name)); 458 459 return parent::__get($name); 460 461EOT; 462 } else { 463 $magicGet .= <<<'EOT' 464 trigger_error(sprintf('Undefined property: %s::$%s', __CLASS__, $name), E_USER_NOTICE); 465 466EOT; 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 500EOT; 501 502 if ( ! empty($lazyPublicProperties)) { 503 $magicSet .= <<<'EOT' 504 if (array_key_exists($name, $this->__getLazyProperties())) { 505 $this->__initializer__ && $this->__initializer__->__invoke($this, '__set', array($name, $value)); 506 507 $this->$name = $value; 508 509 return; 510 } 511 512 513EOT; 514 } 515 516 if ($hasParentSet) { 517 $magicSet .= <<<'EOT' 518 $this->__initializer__ && $this->__initializer__->__invoke($this, '__set', array($name, $value)); 519 520 return parent::__set($name, $value); 521EOT; 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 557EOT; 558 559 if ( ! empty($lazyPublicProperties)) { 560 $magicIsset .= <<<'EOT' 561 if (array_key_exists($name, $this->__getLazyProperties())) { 562 $this->__initializer__ && $this->__initializer__->__invoke($this, '__isset', array($name)); 563 564 return isset($this->$name); 565 } 566 567 568EOT; 569 } 570 571 if ($hasParentIsset) { 572 $magicIsset .= <<<'EOT' 573 $this->__initializer__ && $this->__initializer__->__invoke($this, '__isset', array($name)); 574 575 return parent::__isset($name); 576 577EOT; 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 604EOT; 605 606 if ($hasParentSleep) { 607 return $sleepImpl . <<<'EOT' 608 $properties = array_merge(array('__isInitialized__'), parent::__sleep()); 609 610 if ($this->__isInitialized__) { 611 $properties = array_diff($properties, array_keys($this->__getLazyProperties())); 612 } 613 614 return $properties; 615 } 616EOT; 617 } 618 619 $allProperties = array('__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 array($allProperties); 649 } 650 651 return array($protectedProperties); 652 } 653EOT; 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 = array(); 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 694EOT; 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', array()); 731$callParentClone } 732EOT; 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 = array(); 746 $reflectionMethods = $class->getReflectionClass()->getMethods(\ReflectionMethod::IS_PUBLIC); 747 $skippedMethods = array( 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 .= "\n" . ' {' . "\n"; 782 783 if ($this->isShortIdentifierGetter($method, $class)) { 784 $identifier = lcfirst(substr($name, 3)); 785 $fieldType = $class->getTypeOfField($identifier); 786 $cast = in_array($fieldType, array('integer', 'smallint')) ? '(int) ' : ''; 787 788 $methods .= ' if ($this->__isInitialized__ === false) {' . "\n"; 789 $methods .= ' return ' . $cast . ' parent::' . $method->getName() . "();\n"; 790 $methods .= ' }' . "\n\n"; 791 } 792 793 $invokeParamsString = implode(', ', $this->getParameterNamesForInvoke($method->getParameters())); 794 $callParamsString = implode(', ', $this->getParameterNamesForParentCall($method->getParameters())); 795 796 $methods .= "\n \$this->__initializer__ " 797 . "&& \$this->__initializer__->__invoke(\$this, " . var_export($name, true) 798 . ", array(" . $invokeParamsString . "));" 799 . "\n\n return parent::" . $name . '(' . $callParamsString . ');' 800 . "\n" . ' }' . "\n"; 801 } 802 803 return $methods; 804 } 805 806 /** 807 * Generates the Proxy file name. 808 * 809 * @param string $className 810 * @param string $baseDirectory Optional base directory for proxy file name generation. 811 * If not specified, the directory configured on the Configuration of the 812 * EntityManager will be used by this factory. 813 * 814 * @return string 815 */ 816 public function getProxyFileName($className, $baseDirectory = null) 817 { 818 $baseDirectory = $baseDirectory ?: $this->proxyDirectory; 819 820 return rtrim($baseDirectory, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . Proxy::MARKER 821 . str_replace('\\', '', $className) . '.php'; 822 } 823 824 /** 825 * Checks if the method is a short identifier getter. 826 * 827 * What does this mean? For proxy objects the identifier is already known, 828 * however accessing the getter for this identifier usually triggers the 829 * lazy loading, leading to a query that may not be necessary if only the 830 * ID is interesting for the userland code (for example in views that 831 * generate links to the entity, but do not display anything else). 832 * 833 * @param \ReflectionMethod $method 834 * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class 835 * 836 * @return boolean 837 */ 838 private function isShortIdentifierGetter($method, ClassMetadata $class) 839 { 840 $identifier = lcfirst(substr($method->getName(), 3)); 841 $startLine = $method->getStartLine(); 842 $endLine = $method->getEndLine(); 843 $cheapCheck = ( 844 $method->getNumberOfParameters() == 0 845 && substr($method->getName(), 0, 3) == 'get' 846 && in_array($identifier, $class->getIdentifier(), true) 847 && $class->hasField($identifier) 848 && (($endLine - $startLine) <= 4) 849 ); 850 851 if ($cheapCheck) { 852 $code = file($method->getDeclaringClass()->getFileName()); 853 $code = trim(implode(' ', array_slice($code, $startLine - 1, $endLine - $startLine + 1))); 854 855 $pattern = sprintf(self::PATTERN_MATCH_ID_METHOD, $method->getName(), $identifier); 856 857 if (preg_match($pattern, $code)) { 858 return true; 859 } 860 } 861 862 return false; 863 } 864 865 /** 866 * Generates the list of public properties to be lazy loaded, with their default values. 867 * 868 * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class 869 * 870 * @return mixed[] 871 */ 872 private function getLazyLoadedPublicProperties(ClassMetadata $class) 873 { 874 $defaultProperties = $class->getReflectionClass()->getDefaultProperties(); 875 $properties = array(); 876 877 foreach ($class->getReflectionClass()->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) { 878 $name = $property->getName(); 879 880 if (($class->hasField($name) || $class->hasAssociation($name)) && ! $class->isIdentifier($name)) { 881 $properties[$name] = $defaultProperties[$name]; 882 } 883 } 884 885 return $properties; 886 } 887 888 /** 889 * @param ClassMetadata $class 890 * @param \ReflectionMethod $method 891 * @param \ReflectionParameter[] $parameters 892 * 893 * @return string 894 */ 895 private function buildParametersString(ClassMetadata $class, \ReflectionMethod $method, array $parameters) 896 { 897 $parameterDefinitions = array(); 898 899 /* @var $param \ReflectionParameter */ 900 foreach ($parameters as $param) { 901 $parameterDefinition = ''; 902 903 if ($parameterType = $this->getParameterType($class, $method, $param)) { 904 $parameterDefinition .= $parameterType . ' '; 905 } 906 907 if ($param->isPassedByReference()) { 908 $parameterDefinition .= '&'; 909 } 910 911 if (method_exists($param, 'isVariadic')) { 912 if ($param->isVariadic()) { 913 $parameterDefinition .= '...'; 914 } 915 } 916 917 $parameters[] = '$' . $param->getName(); 918 $parameterDefinition .= '$' . $param->getName(); 919 920 if ($param->isDefaultValueAvailable()) { 921 $parameterDefinition .= ' = ' . var_export($param->getDefaultValue(), true); 922 } 923 924 $parameterDefinitions[] = $parameterDefinition; 925 } 926 927 return implode(', ', $parameterDefinitions); 928 } 929 930 /** 931 * @param ClassMetadata $class 932 * @param \ReflectionMethod $method 933 * @param \ReflectionParameter $parameter 934 * 935 * @return string|null 936 */ 937 private function getParameterType(ClassMetadata $class, \ReflectionMethod $method, \ReflectionParameter $parameter) 938 { 939 940 // We need to pick the type hint class too 941 if ($parameter->isArray()) { 942 return 'array'; 943 } 944 945 if (method_exists($parameter, 'isCallable') && $parameter->isCallable()) { 946 return 'callable'; 947 } 948 949 try { 950 $parameterClass = $parameter->getClass(); 951 952 if ($parameterClass) { 953 return '\\' . $parameterClass->getName(); 954 } 955 } catch (\ReflectionException $previous) { 956 throw UnexpectedValueException::invalidParameterTypeHint( 957 $class->getName(), 958 $method->getName(), 959 $parameter->getName(), 960 $previous 961 ); 962 } 963 964 return null; 965 } 966 967 /** 968 * @param \ReflectionParameter[] $parameters 969 * 970 * @return string[] 971 */ 972 private function getParameterNamesForInvoke(array $parameters) 973 { 974 return array_map( 975 function (\ReflectionParameter $parameter) { 976 return '$' . $parameter->getName(); 977 }, 978 $parameters 979 ); 980 } 981 982 /** 983 * @param \ReflectionParameter[] $parameters 984 * 985 * @return string[] 986 */ 987 private function getParameterNamesForParentCall(array $parameters) 988 { 989 return array_map( 990 function (\ReflectionParameter $parameter) { 991 $name = ''; 992 993 if (method_exists($parameter, 'isVariadic')) { 994 if ($parameter->isVariadic()) { 995 $name .= '...'; 996 } 997 } 998 999 $name .= '$' . $parameter->getName(); 1000 1001 return $name; 1002 }, 1003 $parameters 1004 ); 1005 } 1006} 1007