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\ORM\Tools; 21 22use Doctrine\ORM\Mapping\ClassMetadataInfo; 23use Doctrine\Common\Util\Inflector; 24use Doctrine\DBAL\Types\Type; 25 26/** 27 * Generic class used to generate PHP5 entity classes from ClassMetadataInfo instances. 28 * 29 * [php] 30 * $classes = $em->getClassMetadataFactory()->getAllMetadata(); 31 * 32 * $generator = new \Doctrine\ORM\Tools\EntityGenerator(); 33 * $generator->setGenerateAnnotations(true); 34 * $generator->setGenerateStubMethods(true); 35 * $generator->setRegenerateEntityIfExists(false); 36 * $generator->setUpdateEntityIfExists(true); 37 * $generator->generate($classes, '/path/to/generate/entities'); 38 * 39 * 40 * @link www.doctrine-project.org 41 * @since 2.0 42 * @author Benjamin Eberlei <kontakt@beberlei.de> 43 * @author Guilherme Blanco <guilhermeblanco@hotmail.com> 44 * @author Jonathan Wage <jonwage@gmail.com> 45 * @author Roman Borschel <roman@code-factory.org> 46 */ 47class EntityGenerator 48{ 49 /** 50 * Specifies class fields should be protected. 51 */ 52 const FIELD_VISIBLE_PROTECTED = 'protected'; 53 54 /** 55 * Specifies class fields should be private. 56 */ 57 const FIELD_VISIBLE_PRIVATE = 'private'; 58 59 /** 60 * @var bool 61 */ 62 protected $backupExisting = true; 63 64 /** 65 * The extension to use for written php files. 66 * 67 * @var string 68 */ 69 protected $extension = '.php'; 70 71 /** 72 * Whether or not the current ClassMetadataInfo instance is new or old. 73 * 74 * @var boolean 75 */ 76 protected $isNew = true; 77 78 /** 79 * @var array 80 */ 81 protected $staticReflection = array(); 82 83 /** 84 * Number of spaces to use for indention in generated code. 85 */ 86 protected $numSpaces = 4; 87 88 /** 89 * The actual spaces to use for indention. 90 * 91 * @var string 92 */ 93 protected $spaces = ' '; 94 95 /** 96 * The class all generated entities should extend. 97 * 98 * @var string 99 */ 100 protected $classToExtend; 101 102 /** 103 * Whether or not to generation annotations. 104 * 105 * @var boolean 106 */ 107 protected $generateAnnotations = false; 108 109 /** 110 * @var string 111 */ 112 protected $annotationsPrefix = ''; 113 114 /** 115 * Whether or not to generate sub methods. 116 * 117 * @var boolean 118 */ 119 protected $generateEntityStubMethods = false; 120 121 /** 122 * Whether or not to update the entity class if it exists already. 123 * 124 * @var boolean 125 */ 126 protected $updateEntityIfExists = false; 127 128 /** 129 * Whether or not to re-generate entity class if it exists already. 130 * 131 * @var boolean 132 */ 133 protected $regenerateEntityIfExists = false; 134 135 /** 136 * Visibility of the field 137 * 138 * @var string 139 */ 140 protected $fieldVisibility = 'private'; 141 142 /** 143 * Whether or not to make generated embeddables immutable. 144 * 145 * @var boolean. 146 */ 147 protected $embeddablesImmutable = false; 148 149 /** 150 * Hash-map for handle types. 151 * 152 * @var array 153 */ 154 protected $typeAlias = array( 155 Type::DATETIMETZ => '\DateTime', 156 Type::DATETIME => '\DateTime', 157 Type::DATE => '\DateTime', 158 Type::TIME => '\DateTime', 159 Type::OBJECT => '\stdClass', 160 Type::BIGINT => 'integer', 161 Type::SMALLINT => 'integer', 162 Type::TEXT => 'string', 163 Type::BLOB => 'string', 164 Type::DECIMAL => 'string', 165 Type::JSON_ARRAY => 'array', 166 Type::SIMPLE_ARRAY => 'array', 167 ); 168 169 /** 170 * Hash-map to handle generator types string. 171 * 172 * @var array 173 */ 174 protected static $generatorStrategyMap = array( 175 ClassMetadataInfo::GENERATOR_TYPE_AUTO => 'AUTO', 176 ClassMetadataInfo::GENERATOR_TYPE_SEQUENCE => 'SEQUENCE', 177 ClassMetadataInfo::GENERATOR_TYPE_TABLE => 'TABLE', 178 ClassMetadataInfo::GENERATOR_TYPE_IDENTITY => 'IDENTITY', 179 ClassMetadataInfo::GENERATOR_TYPE_NONE => 'NONE', 180 ClassMetadataInfo::GENERATOR_TYPE_UUID => 'UUID', 181 ClassMetadataInfo::GENERATOR_TYPE_CUSTOM => 'CUSTOM' 182 ); 183 184 /** 185 * Hash-map to handle the change tracking policy string. 186 * 187 * @var array 188 */ 189 protected static $changeTrackingPolicyMap = array( 190 ClassMetadataInfo::CHANGETRACKING_DEFERRED_IMPLICIT => 'DEFERRED_IMPLICIT', 191 ClassMetadataInfo::CHANGETRACKING_DEFERRED_EXPLICIT => 'DEFERRED_EXPLICIT', 192 ClassMetadataInfo::CHANGETRACKING_NOTIFY => 'NOTIFY', 193 ); 194 195 /** 196 * Hash-map to handle the inheritance type string. 197 * 198 * @var array 199 */ 200 protected static $inheritanceTypeMap = array( 201 ClassMetadataInfo::INHERITANCE_TYPE_NONE => 'NONE', 202 ClassMetadataInfo::INHERITANCE_TYPE_JOINED => 'JOINED', 203 ClassMetadataInfo::INHERITANCE_TYPE_SINGLE_TABLE => 'SINGLE_TABLE', 204 ClassMetadataInfo::INHERITANCE_TYPE_TABLE_PER_CLASS => 'TABLE_PER_CLASS', 205 ); 206 207 /** 208 * @var string 209 */ 210 protected static $classTemplate = 211'<?php 212 213<namespace> 214<useStatement> 215<entityAnnotation> 216<entityClassName> 217{ 218<entityBody> 219} 220'; 221 222 /** 223 * @var string 224 */ 225 protected static $getMethodTemplate = 226'/** 227 * <description> 228 * 229 * @return <variableType> 230 */ 231public function <methodName>() 232{ 233<spaces>return $this-><fieldName>; 234}'; 235 236 /** 237 * @var string 238 */ 239 protected static $setMethodTemplate = 240'/** 241 * <description> 242 * 243 * @param <variableType> $<variableName> 244 * 245 * @return <entity> 246 */ 247public function <methodName>(<methodTypeHint>$<variableName><variableDefault>) 248{ 249<spaces>$this-><fieldName> = $<variableName>; 250 251<spaces>return $this; 252}'; 253 254 /** 255 * @var string 256 */ 257 protected static $addMethodTemplate = 258'/** 259 * <description> 260 * 261 * @param <variableType> $<variableName> 262 * 263 * @return <entity> 264 */ 265public function <methodName>(<methodTypeHint>$<variableName>) 266{ 267<spaces>$this-><fieldName>[] = $<variableName>; 268 269<spaces>return $this; 270}'; 271 272 /** 273 * @var string 274 */ 275 protected static $removeMethodTemplate = 276'/** 277 * <description> 278 * 279 * @param <variableType> $<variableName> 280 */ 281public function <methodName>(<methodTypeHint>$<variableName>) 282{ 283<spaces>$this-><fieldName>->removeElement($<variableName>); 284}'; 285 286 /** 287 * @var string 288 */ 289 protected static $lifecycleCallbackMethodTemplate = 290'/** 291 * @<name> 292 */ 293public function <methodName>() 294{ 295<spaces>// Add your code here 296}'; 297 298 /** 299 * @var string 300 */ 301 protected static $constructorMethodTemplate = 302'/** 303 * Constructor 304 */ 305public function __construct() 306{ 307<spaces><collections> 308} 309'; 310 311 /** 312 * @var string 313 */ 314 protected static $embeddableConstructorMethodTemplate = 315'/** 316 * Constructor 317 * 318 * <paramTags> 319 */ 320public function __construct(<params>) 321{ 322<spaces><fields> 323} 324'; 325 326 /** 327 * Constructor. 328 */ 329 public function __construct() 330 { 331 if (version_compare(\Doctrine\Common\Version::VERSION, '2.2.0-DEV', '>=')) { 332 $this->annotationsPrefix = 'ORM\\'; 333 } 334 } 335 336 /** 337 * Generates and writes entity classes for the given array of ClassMetadataInfo instances. 338 * 339 * @param array $metadatas 340 * @param string $outputDirectory 341 * 342 * @return void 343 */ 344 public function generate(array $metadatas, $outputDirectory) 345 { 346 foreach ($metadatas as $metadata) { 347 $this->writeEntityClass($metadata, $outputDirectory); 348 } 349 } 350 351 /** 352 * Generates and writes entity class to disk for the given ClassMetadataInfo instance. 353 * 354 * @param ClassMetadataInfo $metadata 355 * @param string $outputDirectory 356 * 357 * @return void 358 * 359 * @throws \RuntimeException 360 */ 361 public function writeEntityClass(ClassMetadataInfo $metadata, $outputDirectory) 362 { 363 $path = $outputDirectory . '/' . str_replace('\\', DIRECTORY_SEPARATOR, $metadata->name) . $this->extension; 364 $dir = dirname($path); 365 366 if ( ! is_dir($dir)) { 367 mkdir($dir, 0775, true); 368 } 369 370 $this->isNew = !file_exists($path) || (file_exists($path) && $this->regenerateEntityIfExists); 371 372 if ( ! $this->isNew) { 373 $this->parseTokensInEntityFile(file_get_contents($path)); 374 } else { 375 $this->staticReflection[$metadata->name] = array('properties' => array(), 'methods' => array()); 376 } 377 378 if ($this->backupExisting && file_exists($path)) { 379 $backupPath = dirname($path) . DIRECTORY_SEPARATOR . basename($path) . "~"; 380 if (!copy($path, $backupPath)) { 381 throw new \RuntimeException("Attempt to backup overwritten entity file but copy operation failed."); 382 } 383 } 384 385 // If entity doesn't exist or we're re-generating the entities entirely 386 if ($this->isNew) { 387 file_put_contents($path, $this->generateEntityClass($metadata)); 388 // If entity exists and we're allowed to update the entity class 389 } elseif ( ! $this->isNew && $this->updateEntityIfExists) { 390 file_put_contents($path, $this->generateUpdatedEntityClass($metadata, $path)); 391 } 392 chmod($path, 0664); 393 } 394 395 /** 396 * Generates a PHP5 Doctrine 2 entity class from the given ClassMetadataInfo instance. 397 * 398 * @param ClassMetadataInfo $metadata 399 * 400 * @return string 401 */ 402 public function generateEntityClass(ClassMetadataInfo $metadata) 403 { 404 $placeHolders = array( 405 '<namespace>', 406 '<useStatement>', 407 '<entityAnnotation>', 408 '<entityClassName>', 409 '<entityBody>' 410 ); 411 412 $replacements = array( 413 $this->generateEntityNamespace($metadata), 414 $this->generateEntityUse(), 415 $this->generateEntityDocBlock($metadata), 416 $this->generateEntityClassName($metadata), 417 $this->generateEntityBody($metadata) 418 ); 419 420 $code = str_replace($placeHolders, $replacements, static::$classTemplate) . "\n"; 421 422 return str_replace('<spaces>', $this->spaces, $code); 423 } 424 425 /** 426 * Generates the updated code for the given ClassMetadataInfo and entity at path. 427 * 428 * @param ClassMetadataInfo $metadata 429 * @param string $path 430 * 431 * @return string 432 */ 433 public function generateUpdatedEntityClass(ClassMetadataInfo $metadata, $path) 434 { 435 $currentCode = file_get_contents($path); 436 437 $body = $this->generateEntityBody($metadata); 438 $body = str_replace('<spaces>', $this->spaces, $body); 439 $last = strrpos($currentCode, '}'); 440 441 return substr($currentCode, 0, $last) . $body . (strlen($body) > 0 ? "\n" : '') . "}\n"; 442 } 443 444 /** 445 * Sets the number of spaces the exported class should have. 446 * 447 * @param integer $numSpaces 448 * 449 * @return void 450 */ 451 public function setNumSpaces($numSpaces) 452 { 453 $this->spaces = str_repeat(' ', $numSpaces); 454 $this->numSpaces = $numSpaces; 455 } 456 457 /** 458 * Sets the extension to use when writing php files to disk. 459 * 460 * @param string $extension 461 * 462 * @return void 463 */ 464 public function setExtension($extension) 465 { 466 $this->extension = $extension; 467 } 468 469 /** 470 * Sets the name of the class the generated classes should extend from. 471 * 472 * @param string $classToExtend 473 * 474 * @return void 475 */ 476 public function setClassToExtend($classToExtend) 477 { 478 $this->classToExtend = $classToExtend; 479 } 480 481 /** 482 * Sets whether or not to generate annotations for the entity. 483 * 484 * @param bool $bool 485 * 486 * @return void 487 */ 488 public function setGenerateAnnotations($bool) 489 { 490 $this->generateAnnotations = $bool; 491 } 492 493 /** 494 * Sets the class fields visibility for the entity (can either be private or protected). 495 * 496 * @param bool $visibility 497 * 498 * @return void 499 * 500 * @throws \InvalidArgumentException 501 */ 502 public function setFieldVisibility($visibility) 503 { 504 if ($visibility !== static::FIELD_VISIBLE_PRIVATE && $visibility !== static::FIELD_VISIBLE_PROTECTED) { 505 throw new \InvalidArgumentException('Invalid provided visibility (only private and protected are allowed): ' . $visibility); 506 } 507 508 $this->fieldVisibility = $visibility; 509 } 510 511 /** 512 * Sets whether or not to generate immutable embeddables. 513 * 514 * @param boolean $embeddablesImmutable 515 */ 516 public function setEmbeddablesImmutable($embeddablesImmutable) 517 { 518 $this->embeddablesImmutable = (boolean) $embeddablesImmutable; 519 } 520 521 /** 522 * Sets an annotation prefix. 523 * 524 * @param string $prefix 525 * 526 * @return void 527 */ 528 public function setAnnotationPrefix($prefix) 529 { 530 $this->annotationsPrefix = $prefix; 531 } 532 533 /** 534 * Sets whether or not to try and update the entity if it already exists. 535 * 536 * @param bool $bool 537 * 538 * @return void 539 */ 540 public function setUpdateEntityIfExists($bool) 541 { 542 $this->updateEntityIfExists = $bool; 543 } 544 545 /** 546 * Sets whether or not to regenerate the entity if it exists. 547 * 548 * @param bool $bool 549 * 550 * @return void 551 */ 552 public function setRegenerateEntityIfExists($bool) 553 { 554 $this->regenerateEntityIfExists = $bool; 555 } 556 557 /** 558 * Sets whether or not to generate stub methods for the entity. 559 * 560 * @param bool $bool 561 * 562 * @return void 563 */ 564 public function setGenerateStubMethods($bool) 565 { 566 $this->generateEntityStubMethods = $bool; 567 } 568 569 /** 570 * Should an existing entity be backed up if it already exists? 571 * 572 * @param bool $bool 573 * 574 * @return void 575 */ 576 public function setBackupExisting($bool) 577 { 578 $this->backupExisting = $bool; 579 } 580 581 /** 582 * @param string $type 583 * 584 * @return string 585 */ 586 protected function getType($type) 587 { 588 if (isset($this->typeAlias[$type])) { 589 return $this->typeAlias[$type]; 590 } 591 592 return $type; 593 } 594 595 /** 596 * @param ClassMetadataInfo $metadata 597 * 598 * @return string 599 */ 600 protected function generateEntityNamespace(ClassMetadataInfo $metadata) 601 { 602 if ($this->hasNamespace($metadata)) { 603 return 'namespace ' . $this->getNamespace($metadata) .';'; 604 } 605 } 606 607 protected function generateEntityUse() 608 { 609 if ($this->generateAnnotations) { 610 return "\n".'use Doctrine\ORM\Mapping as ORM;'."\n"; 611 } else { 612 return ""; 613 } 614 } 615 616 /** 617 * @param ClassMetadataInfo $metadata 618 * 619 * @return string 620 */ 621 protected function generateEntityClassName(ClassMetadataInfo $metadata) 622 { 623 return 'class ' . $this->getClassName($metadata) . 624 ($this->extendsClass() ? ' extends ' . $this->getClassToExtendName() : null); 625 } 626 627 /** 628 * @param ClassMetadataInfo $metadata 629 * 630 * @return string 631 */ 632 protected function generateEntityBody(ClassMetadataInfo $metadata) 633 { 634 $fieldMappingProperties = $this->generateEntityFieldMappingProperties($metadata); 635 $embeddedProperties = $this->generateEntityEmbeddedProperties($metadata); 636 $associationMappingProperties = $this->generateEntityAssociationMappingProperties($metadata); 637 $stubMethods = $this->generateEntityStubMethods ? $this->generateEntityStubMethods($metadata) : null; 638 $lifecycleCallbackMethods = $this->generateEntityLifecycleCallbackMethods($metadata); 639 640 $code = array(); 641 642 if ($fieldMappingProperties) { 643 $code[] = $fieldMappingProperties; 644 } 645 646 if ($embeddedProperties) { 647 $code[] = $embeddedProperties; 648 } 649 650 if ($associationMappingProperties) { 651 $code[] = $associationMappingProperties; 652 } 653 654 $code[] = $this->generateEntityConstructor($metadata); 655 656 if ($stubMethods) { 657 $code[] = $stubMethods; 658 } 659 660 if ($lifecycleCallbackMethods) { 661 $code[] = $lifecycleCallbackMethods; 662 } 663 664 return implode("\n", $code); 665 } 666 667 /** 668 * @param ClassMetadataInfo $metadata 669 * 670 * @return string 671 */ 672 protected function generateEntityConstructor(ClassMetadataInfo $metadata) 673 { 674 if ($this->hasMethod('__construct', $metadata)) { 675 return ''; 676 } 677 678 if ($metadata->isEmbeddedClass && $this->embeddablesImmutable) { 679 return $this->generateEmbeddableConstructor($metadata); 680 } 681 682 $collections = array(); 683 684 foreach ($metadata->associationMappings as $mapping) { 685 if ($mapping['type'] & ClassMetadataInfo::TO_MANY) { 686 $collections[] = '$this->'.$mapping['fieldName'].' = new \Doctrine\Common\Collections\ArrayCollection();'; 687 } 688 } 689 690 if ($collections) { 691 return $this->prefixCodeWithSpaces(str_replace("<collections>", implode("\n".$this->spaces, $collections), static::$constructorMethodTemplate)); 692 } 693 694 return ''; 695 } 696 697 /** 698 * @param ClassMetadataInfo $metadata 699 * 700 * @return string 701 */ 702 private function generateEmbeddableConstructor(ClassMetadataInfo $metadata) 703 { 704 $paramTypes = array(); 705 $paramVariables = array(); 706 $params = array(); 707 $fields = array(); 708 709 // Resort fields to put optional fields at the end of the method signature. 710 $requiredFields = array(); 711 $optionalFields = array(); 712 713 foreach ($metadata->fieldMappings as $fieldMapping) { 714 if (empty($fieldMapping['nullable'])) { 715 $requiredFields[] = $fieldMapping; 716 717 continue; 718 } 719 720 $optionalFields[] = $fieldMapping; 721 } 722 723 $fieldMappings = array_merge($requiredFields, $optionalFields); 724 725 foreach ($metadata->embeddedClasses as $fieldName => $embeddedClass) { 726 $paramType = '\\' . ltrim($embeddedClass['class'], '\\'); 727 $paramVariable = '$' . $fieldName; 728 729 $paramTypes[] = $paramType; 730 $paramVariables[] = $paramVariable; 731 $params[] = $paramType . ' ' . $paramVariable; 732 $fields[] = '$this->' . $fieldName . ' = ' . $paramVariable . ';'; 733 } 734 735 foreach ($fieldMappings as $fieldMapping) { 736 if (isset($fieldMapping['declaredField']) && 737 isset($metadata->embeddedClasses[$fieldMapping['declaredField']]) 738 ) { 739 continue; 740 } 741 742 $paramTypes[] = $this->getType($fieldMapping['type']) . (!empty($fieldMapping['nullable']) ? '|null' : ''); 743 $param = '$' . $fieldMapping['fieldName']; 744 $paramVariables[] = $param; 745 746 if ($fieldMapping['type'] === 'datetime') { 747 $param = $this->getType($fieldMapping['type']) . ' ' . $param; 748 } 749 750 if (!empty($fieldMapping['nullable'])) { 751 $param .= ' = null'; 752 } 753 754 $params[] = $param; 755 756 $fields[] = '$this->' . $fieldMapping['fieldName'] . ' = $' . $fieldMapping['fieldName'] . ';'; 757 } 758 759 $maxParamTypeLength = max(array_map('strlen', $paramTypes)); 760 $paramTags = array_map( 761 function ($type, $variable) use ($maxParamTypeLength) { 762 return '@param ' . $type . str_repeat(' ', $maxParamTypeLength - strlen($type) + 1) . $variable; 763 }, 764 $paramTypes, 765 $paramVariables 766 ); 767 768 // Generate multi line constructor if the signature exceeds 120 characters. 769 if (array_sum(array_map('strlen', $params)) + count($params) * 2 + 29 > 120) { 770 $delimiter = "\n" . $this->spaces; 771 $params = $delimiter . implode(',' . $delimiter, $params) . "\n"; 772 } else { 773 $params = implode(', ', $params); 774 } 775 776 $replacements = array( 777 '<paramTags>' => implode("\n * ", $paramTags), 778 '<params>' => $params, 779 '<fields>' => implode("\n" . $this->spaces, $fields), 780 ); 781 782 $constructor = str_replace( 783 array_keys($replacements), 784 array_values($replacements), 785 static::$embeddableConstructorMethodTemplate 786 ); 787 788 return $this->prefixCodeWithSpaces($constructor); 789 } 790 791 /** 792 * @todo this won't work if there is a namespace in brackets and a class outside of it. 793 * 794 * @param string $src 795 * 796 * @return void 797 */ 798 protected function parseTokensInEntityFile($src) 799 { 800 $tokens = token_get_all($src); 801 $lastSeenNamespace = ""; 802 $lastSeenClass = false; 803 804 $inNamespace = false; 805 $inClass = false; 806 807 for ($i = 0; $i < count($tokens); $i++) { 808 $token = $tokens[$i]; 809 if (in_array($token[0], array(T_WHITESPACE, T_COMMENT, T_DOC_COMMENT))) { 810 continue; 811 } 812 813 if ($inNamespace) { 814 if ($token[0] == T_NS_SEPARATOR || $token[0] == T_STRING) { 815 $lastSeenNamespace .= $token[1]; 816 } elseif (is_string($token) && in_array($token, array(';', '{'))) { 817 $inNamespace = false; 818 } 819 } 820 821 if ($inClass) { 822 $inClass = false; 823 $lastSeenClass = $lastSeenNamespace . ($lastSeenNamespace ? '\\' : '') . $token[1]; 824 $this->staticReflection[$lastSeenClass]['properties'] = array(); 825 $this->staticReflection[$lastSeenClass]['methods'] = array(); 826 } 827 828 if ($token[0] == T_NAMESPACE) { 829 $lastSeenNamespace = ""; 830 $inNamespace = true; 831 } elseif ($token[0] == T_CLASS && $tokens[$i-1][0] != T_DOUBLE_COLON) { 832 $inClass = true; 833 } elseif ($token[0] == T_FUNCTION) { 834 if ($tokens[$i+2][0] == T_STRING) { 835 $this->staticReflection[$lastSeenClass]['methods'][] = strtolower($tokens[$i+2][1]); 836 } elseif ($tokens[$i+2] == "&" && $tokens[$i+3][0] == T_STRING) { 837 $this->staticReflection[$lastSeenClass]['methods'][] = strtolower($tokens[$i+3][1]); 838 } 839 } elseif (in_array($token[0], array(T_VAR, T_PUBLIC, T_PRIVATE, T_PROTECTED)) && $tokens[$i+2][0] != T_FUNCTION) { 840 $this->staticReflection[$lastSeenClass]['properties'][] = substr($tokens[$i+2][1], 1); 841 } 842 } 843 } 844 845 /** 846 * @param string $property 847 * @param ClassMetadataInfo $metadata 848 * 849 * @return bool 850 */ 851 protected function hasProperty($property, ClassMetadataInfo $metadata) 852 { 853 if ($this->extendsClass() || (!$this->isNew && class_exists($metadata->name))) { 854 // don't generate property if its already on the base class. 855 $reflClass = new \ReflectionClass($this->getClassToExtend() ?: $metadata->name); 856 if ($reflClass->hasProperty($property)) { 857 return true; 858 } 859 } 860 861 // check traits for existing property 862 foreach ($this->getTraits($metadata) as $trait) { 863 if ($trait->hasProperty($property)) { 864 return true; 865 } 866 } 867 868 return ( 869 isset($this->staticReflection[$metadata->name]) && 870 in_array($property, $this->staticReflection[$metadata->name]['properties']) 871 ); 872 } 873 874 /** 875 * @param string $method 876 * @param ClassMetadataInfo $metadata 877 * 878 * @return bool 879 */ 880 protected function hasMethod($method, ClassMetadataInfo $metadata) 881 { 882 if ($this->extendsClass() || (!$this->isNew && class_exists($metadata->name))) { 883 // don't generate method if its already on the base class. 884 $reflClass = new \ReflectionClass($this->getClassToExtend() ?: $metadata->name); 885 886 if ($reflClass->hasMethod($method)) { 887 return true; 888 } 889 } 890 891 // check traits for existing method 892 foreach ($this->getTraits($metadata) as $trait) { 893 if ($trait->hasMethod($method)) { 894 return true; 895 } 896 } 897 898 return ( 899 isset($this->staticReflection[$metadata->name]) && 900 in_array(strtolower($method), $this->staticReflection[$metadata->name]['methods']) 901 ); 902 } 903 904 /** 905 * @param ClassMetadataInfo $metadata 906 * 907 * @return array 908 */ 909 protected function getTraits(ClassMetadataInfo $metadata) 910 { 911 if (! ($metadata->reflClass !== null || class_exists($metadata->name))) { 912 return []; 913 } 914 915 $reflClass = $metadata->reflClass === null 916 ? new \ReflectionClass($metadata->name) 917 : $metadata->reflClass; 918 919 $traits = array(); 920 921 while ($reflClass !== false) { 922 $traits = array_merge($traits, $reflClass->getTraits()); 923 924 $reflClass = $reflClass->getParentClass(); 925 } 926 927 return $traits; 928 } 929 930 /** 931 * @param ClassMetadataInfo $metadata 932 * 933 * @return bool 934 */ 935 protected function hasNamespace(ClassMetadataInfo $metadata) 936 { 937 return strpos($metadata->name, '\\') ? true : false; 938 } 939 940 /** 941 * @return bool 942 */ 943 protected function extendsClass() 944 { 945 return $this->classToExtend ? true : false; 946 } 947 948 /** 949 * @return string 950 */ 951 protected function getClassToExtend() 952 { 953 return $this->classToExtend; 954 } 955 956 /** 957 * @return string 958 */ 959 protected function getClassToExtendName() 960 { 961 $refl = new \ReflectionClass($this->getClassToExtend()); 962 963 return '\\' . $refl->getName(); 964 } 965 966 /** 967 * @param ClassMetadataInfo $metadata 968 * 969 * @return string 970 */ 971 protected function getClassName(ClassMetadataInfo $metadata) 972 { 973 return ($pos = strrpos($metadata->name, '\\')) 974 ? substr($metadata->name, $pos + 1, strlen($metadata->name)) : $metadata->name; 975 } 976 977 /** 978 * @param ClassMetadataInfo $metadata 979 * 980 * @return string 981 */ 982 protected function getNamespace(ClassMetadataInfo $metadata) 983 { 984 return substr($metadata->name, 0, strrpos($metadata->name, '\\')); 985 } 986 987 /** 988 * @param ClassMetadataInfo $metadata 989 * 990 * @return string 991 */ 992 protected function generateEntityDocBlock(ClassMetadataInfo $metadata) 993 { 994 $lines = array(); 995 $lines[] = '/**'; 996 $lines[] = ' * ' . $this->getClassName($metadata); 997 998 if ($this->generateAnnotations) { 999 $lines[] = ' *'; 1000 1001 $methods = array( 1002 'generateTableAnnotation', 1003 'generateInheritanceAnnotation', 1004 'generateDiscriminatorColumnAnnotation', 1005 'generateDiscriminatorMapAnnotation', 1006 'generateEntityAnnotation', 1007 ); 1008 1009 foreach ($methods as $method) { 1010 if ($code = $this->$method($metadata)) { 1011 $lines[] = ' * ' . $code; 1012 } 1013 } 1014 1015 if (isset($metadata->lifecycleCallbacks) && $metadata->lifecycleCallbacks) { 1016 $lines[] = ' * @' . $this->annotationsPrefix . 'HasLifecycleCallbacks'; 1017 } 1018 } 1019 1020 $lines[] = ' */'; 1021 1022 return implode("\n", $lines); 1023 } 1024 1025 /** 1026 * @param ClassMetadataInfo $metadata 1027 * 1028 * @return string 1029 */ 1030 protected function generateEntityAnnotation(ClassMetadataInfo $metadata) 1031 { 1032 $prefix = '@' . $this->annotationsPrefix; 1033 1034 if ($metadata->isEmbeddedClass) { 1035 return $prefix . 'Embeddable'; 1036 } 1037 1038 $customRepository = $metadata->customRepositoryClassName 1039 ? '(repositoryClass="' . $metadata->customRepositoryClassName . '")' 1040 : ''; 1041 1042 return $prefix . ($metadata->isMappedSuperclass ? 'MappedSuperclass' : 'Entity') . $customRepository; 1043 } 1044 1045 /** 1046 * @param ClassMetadataInfo $metadata 1047 * 1048 * @return string 1049 */ 1050 protected function generateTableAnnotation($metadata) 1051 { 1052 if ($metadata->isEmbeddedClass) { 1053 return ''; 1054 } 1055 1056 $table = array(); 1057 1058 if (isset($metadata->table['schema'])) { 1059 $table[] = 'schema="' . $metadata->table['schema'] . '"'; 1060 } 1061 1062 if (isset($metadata->table['name'])) { 1063 $table[] = 'name="' . $metadata->table['name'] . '"'; 1064 } 1065 1066 if (isset($metadata->table['options']) && $metadata->table['options']) { 1067 $table[] = 'options={' . $this->exportTableOptions((array) $metadata->table['options']) . '}'; 1068 } 1069 1070 if (isset($metadata->table['uniqueConstraints']) && $metadata->table['uniqueConstraints']) { 1071 $constraints = $this->generateTableConstraints('UniqueConstraint', $metadata->table['uniqueConstraints']); 1072 $table[] = 'uniqueConstraints={' . $constraints . '}'; 1073 } 1074 1075 if (isset($metadata->table['indexes']) && $metadata->table['indexes']) { 1076 $constraints = $this->generateTableConstraints('Index', $metadata->table['indexes']); 1077 $table[] = 'indexes={' . $constraints . '}'; 1078 } 1079 1080 return '@' . $this->annotationsPrefix . 'Table(' . implode(', ', $table) . ')'; 1081 } 1082 1083 /** 1084 * @param string $constraintName 1085 * @param array $constraints 1086 * 1087 * @return string 1088 */ 1089 protected function generateTableConstraints($constraintName, $constraints) 1090 { 1091 $annotations = array(); 1092 foreach ($constraints as $name => $constraint) { 1093 $columns = array(); 1094 foreach ($constraint['columns'] as $column) { 1095 $columns[] = '"' . $column . '"'; 1096 } 1097 $annotations[] = '@' . $this->annotationsPrefix . $constraintName . '(name="' . $name . '", columns={' . implode(', ', $columns) . '})'; 1098 } 1099 return implode(', ', $annotations); 1100 } 1101 1102 /** 1103 * @param ClassMetadataInfo $metadata 1104 * 1105 * @return string 1106 */ 1107 protected function generateInheritanceAnnotation($metadata) 1108 { 1109 if ($metadata->inheritanceType != ClassMetadataInfo::INHERITANCE_TYPE_NONE) { 1110 return '@' . $this->annotationsPrefix . 'InheritanceType("'.$this->getInheritanceTypeString($metadata->inheritanceType).'")'; 1111 } 1112 } 1113 1114 /** 1115 * @param ClassMetadataInfo $metadata 1116 * 1117 * @return string 1118 */ 1119 protected function generateDiscriminatorColumnAnnotation($metadata) 1120 { 1121 if ($metadata->inheritanceType != ClassMetadataInfo::INHERITANCE_TYPE_NONE) { 1122 $discrColumn = $metadata->discriminatorColumn; 1123 $columnDefinition = 'name="' . $discrColumn['name'] 1124 . '", type="' . $discrColumn['type'] 1125 . '", length=' . $discrColumn['length']; 1126 1127 return '@' . $this->annotationsPrefix . 'DiscriminatorColumn(' . $columnDefinition . ')'; 1128 } 1129 } 1130 1131 /** 1132 * @param ClassMetadataInfo $metadata 1133 * 1134 * @return string 1135 */ 1136 protected function generateDiscriminatorMapAnnotation($metadata) 1137 { 1138 if ($metadata->inheritanceType != ClassMetadataInfo::INHERITANCE_TYPE_NONE) { 1139 $inheritanceClassMap = array(); 1140 1141 foreach ($metadata->discriminatorMap as $type => $class) { 1142 $inheritanceClassMap[] .= '"' . $type . '" = "' . $class . '"'; 1143 } 1144 1145 return '@' . $this->annotationsPrefix . 'DiscriminatorMap({' . implode(', ', $inheritanceClassMap) . '})'; 1146 } 1147 } 1148 1149 /** 1150 * @param ClassMetadataInfo $metadata 1151 * 1152 * @return string 1153 */ 1154 protected function generateEntityStubMethods(ClassMetadataInfo $metadata) 1155 { 1156 $methods = array(); 1157 1158 foreach ($metadata->fieldMappings as $fieldMapping) { 1159 if (isset($fieldMapping['declaredField']) && 1160 isset($metadata->embeddedClasses[$fieldMapping['declaredField']]) 1161 ) { 1162 continue; 1163 } 1164 1165 if (( ! isset($fieldMapping['id']) || 1166 ! $fieldMapping['id'] || 1167 $metadata->generatorType == ClassMetadataInfo::GENERATOR_TYPE_NONE 1168 ) && (! $metadata->isEmbeddedClass || ! $this->embeddablesImmutable) 1169 ) { 1170 if ($code = $this->generateEntityStubMethod($metadata, 'set', $fieldMapping['fieldName'], $fieldMapping['type'])) { 1171 $methods[] = $code; 1172 } 1173 } 1174 1175 if ($code = $this->generateEntityStubMethod($metadata, 'get', $fieldMapping['fieldName'], $fieldMapping['type'])) { 1176 $methods[] = $code; 1177 } 1178 } 1179 1180 foreach ($metadata->embeddedClasses as $fieldName => $embeddedClass) { 1181 if (isset($embeddedClass['declaredField'])) { 1182 continue; 1183 } 1184 1185 if ( ! $metadata->isEmbeddedClass || ! $this->embeddablesImmutable) { 1186 if ($code = $this->generateEntityStubMethod($metadata, 'set', $fieldName, $embeddedClass['class'])) { 1187 $methods[] = $code; 1188 } 1189 } 1190 1191 if ($code = $this->generateEntityStubMethod($metadata, 'get', $fieldName, $embeddedClass['class'])) { 1192 $methods[] = $code; 1193 } 1194 } 1195 1196 foreach ($metadata->associationMappings as $associationMapping) { 1197 if ($associationMapping['type'] & ClassMetadataInfo::TO_ONE) { 1198 $nullable = $this->isAssociationIsNullable($associationMapping) ? 'null' : null; 1199 if ($code = $this->generateEntityStubMethod($metadata, 'set', $associationMapping['fieldName'], $associationMapping['targetEntity'], $nullable)) { 1200 $methods[] = $code; 1201 } 1202 if ($code = $this->generateEntityStubMethod($metadata, 'get', $associationMapping['fieldName'], $associationMapping['targetEntity'])) { 1203 $methods[] = $code; 1204 } 1205 } elseif ($associationMapping['type'] & ClassMetadataInfo::TO_MANY) { 1206 if ($code = $this->generateEntityStubMethod($metadata, 'add', $associationMapping['fieldName'], $associationMapping['targetEntity'])) { 1207 $methods[] = $code; 1208 } 1209 if ($code = $this->generateEntityStubMethod($metadata, 'remove', $associationMapping['fieldName'], $associationMapping['targetEntity'])) { 1210 $methods[] = $code; 1211 } 1212 if ($code = $this->generateEntityStubMethod($metadata, 'get', $associationMapping['fieldName'], 'Doctrine\Common\Collections\Collection')) { 1213 $methods[] = $code; 1214 } 1215 } 1216 } 1217 1218 return implode("\n\n", $methods); 1219 } 1220 1221 /** 1222 * @param array $associationMapping 1223 * 1224 * @return bool 1225 */ 1226 protected function isAssociationIsNullable($associationMapping) 1227 { 1228 if (isset($associationMapping['id']) && $associationMapping['id']) { 1229 return false; 1230 } 1231 1232 if (isset($associationMapping['joinColumns'])) { 1233 $joinColumns = $associationMapping['joinColumns']; 1234 } else { 1235 //@todo there is no way to retrieve targetEntity metadata 1236 $joinColumns = array(); 1237 } 1238 1239 foreach ($joinColumns as $joinColumn) { 1240 if(isset($joinColumn['nullable']) && !$joinColumn['nullable']) { 1241 return false; 1242 } 1243 } 1244 1245 return true; 1246 } 1247 1248 /** 1249 * @param ClassMetadataInfo $metadata 1250 * 1251 * @return string 1252 */ 1253 protected function generateEntityLifecycleCallbackMethods(ClassMetadataInfo $metadata) 1254 { 1255 if (isset($metadata->lifecycleCallbacks) && $metadata->lifecycleCallbacks) { 1256 $methods = array(); 1257 1258 foreach ($metadata->lifecycleCallbacks as $name => $callbacks) { 1259 foreach ($callbacks as $callback) { 1260 if ($code = $this->generateLifecycleCallbackMethod($name, $callback, $metadata)) { 1261 $methods[] = $code; 1262 } 1263 } 1264 } 1265 1266 return implode("\n\n", $methods); 1267 } 1268 1269 return ""; 1270 } 1271 1272 /** 1273 * @param ClassMetadataInfo $metadata 1274 * 1275 * @return string 1276 */ 1277 protected function generateEntityAssociationMappingProperties(ClassMetadataInfo $metadata) 1278 { 1279 $lines = array(); 1280 1281 foreach ($metadata->associationMappings as $associationMapping) { 1282 if ($this->hasProperty($associationMapping['fieldName'], $metadata)) { 1283 continue; 1284 } 1285 1286 $lines[] = $this->generateAssociationMappingPropertyDocBlock($associationMapping, $metadata); 1287 $lines[] = $this->spaces . $this->fieldVisibility . ' $' . $associationMapping['fieldName'] 1288 . ($associationMapping['type'] == 'manyToMany' ? ' = array()' : null) . ";\n"; 1289 } 1290 1291 return implode("\n", $lines); 1292 } 1293 1294 /** 1295 * @param ClassMetadataInfo $metadata 1296 * 1297 * @return string 1298 */ 1299 protected function generateEntityFieldMappingProperties(ClassMetadataInfo $metadata) 1300 { 1301 $lines = array(); 1302 1303 foreach ($metadata->fieldMappings as $fieldMapping) { 1304 if ($this->hasProperty($fieldMapping['fieldName'], $metadata) || 1305 $metadata->isInheritedField($fieldMapping['fieldName']) || 1306 ( 1307 isset($fieldMapping['declaredField']) && 1308 isset($metadata->embeddedClasses[$fieldMapping['declaredField']]) 1309 ) 1310 ) { 1311 continue; 1312 } 1313 1314 $lines[] = $this->generateFieldMappingPropertyDocBlock($fieldMapping, $metadata); 1315 $lines[] = $this->spaces . $this->fieldVisibility . ' $' . $fieldMapping['fieldName'] 1316 . (isset($fieldMapping['options']['default']) ? ' = ' . var_export($fieldMapping['options']['default'], true) : null) . ";\n"; 1317 } 1318 1319 return implode("\n", $lines); 1320 } 1321 1322 /** 1323 * @param ClassMetadataInfo $metadata 1324 * 1325 * @return string 1326 */ 1327 protected function generateEntityEmbeddedProperties(ClassMetadataInfo $metadata) 1328 { 1329 $lines = array(); 1330 1331 foreach ($metadata->embeddedClasses as $fieldName => $embeddedClass) { 1332 if (isset($embeddedClass['declaredField']) || $this->hasProperty($fieldName, $metadata)) { 1333 continue; 1334 } 1335 1336 $lines[] = $this->generateEmbeddedPropertyDocBlock($embeddedClass); 1337 $lines[] = $this->spaces . $this->fieldVisibility . ' $' . $fieldName . ";\n"; 1338 } 1339 1340 return implode("\n", $lines); 1341 } 1342 1343 /** 1344 * @param ClassMetadataInfo $metadata 1345 * @param string $type 1346 * @param string $fieldName 1347 * @param string|null $typeHint 1348 * @param string|null $defaultValue 1349 * 1350 * @return string 1351 */ 1352 protected function generateEntityStubMethod(ClassMetadataInfo $metadata, $type, $fieldName, $typeHint = null, $defaultValue = null) 1353 { 1354 $methodName = $type . Inflector::classify($fieldName); 1355 $variableName = Inflector::camelize($fieldName); 1356 if (in_array($type, array("add", "remove"))) { 1357 $methodName = Inflector::singularize($methodName); 1358 $variableName = Inflector::singularize($variableName); 1359 } 1360 1361 if ($this->hasMethod($methodName, $metadata)) { 1362 return ''; 1363 } 1364 $this->staticReflection[$metadata->name]['methods'][] = strtolower($methodName); 1365 1366 $var = sprintf('%sMethodTemplate', $type); 1367 $template = static::$$var; 1368 1369 $methodTypeHint = null; 1370 $types = Type::getTypesMap(); 1371 $variableType = $typeHint ? $this->getType($typeHint) : null; 1372 1373 if ($typeHint && ! isset($types[$typeHint])) { 1374 $variableType = '\\' . ltrim($variableType, '\\'); 1375 $methodTypeHint = '\\' . $typeHint . ' '; 1376 } 1377 1378 $replacements = array( 1379 '<description>' => ucfirst($type) . ' ' . $variableName, 1380 '<methodTypeHint>' => $methodTypeHint, 1381 '<variableType>' => $variableType, 1382 '<variableName>' => $variableName, 1383 '<methodName>' => $methodName, 1384 '<fieldName>' => $fieldName, 1385 '<variableDefault>' => ($defaultValue !== null ) ? (' = '.$defaultValue) : '', 1386 '<entity>' => $this->getClassName($metadata) 1387 ); 1388 1389 $method = str_replace( 1390 array_keys($replacements), 1391 array_values($replacements), 1392 $template 1393 ); 1394 1395 return $this->prefixCodeWithSpaces($method); 1396 } 1397 1398 /** 1399 * @param string $name 1400 * @param string $methodName 1401 * @param ClassMetadataInfo $metadata 1402 * 1403 * @return string 1404 */ 1405 protected function generateLifecycleCallbackMethod($name, $methodName, $metadata) 1406 { 1407 if ($this->hasMethod($methodName, $metadata)) { 1408 return ''; 1409 } 1410 $this->staticReflection[$metadata->name]['methods'][] = $methodName; 1411 1412 $replacements = array( 1413 '<name>' => $this->annotationsPrefix . ucfirst($name), 1414 '<methodName>' => $methodName, 1415 ); 1416 1417 $method = str_replace( 1418 array_keys($replacements), 1419 array_values($replacements), 1420 static::$lifecycleCallbackMethodTemplate 1421 ); 1422 1423 return $this->prefixCodeWithSpaces($method); 1424 } 1425 1426 /** 1427 * @param array $joinColumn 1428 * 1429 * @return string 1430 */ 1431 protected function generateJoinColumnAnnotation(array $joinColumn) 1432 { 1433 $joinColumnAnnot = array(); 1434 1435 if (isset($joinColumn['name'])) { 1436 $joinColumnAnnot[] = 'name="' . $joinColumn['name'] . '"'; 1437 } 1438 1439 if (isset($joinColumn['referencedColumnName'])) { 1440 $joinColumnAnnot[] = 'referencedColumnName="' . $joinColumn['referencedColumnName'] . '"'; 1441 } 1442 1443 if (isset($joinColumn['unique']) && $joinColumn['unique']) { 1444 $joinColumnAnnot[] = 'unique=' . ($joinColumn['unique'] ? 'true' : 'false'); 1445 } 1446 1447 if (isset($joinColumn['nullable'])) { 1448 $joinColumnAnnot[] = 'nullable=' . ($joinColumn['nullable'] ? 'true' : 'false'); 1449 } 1450 1451 if (isset($joinColumn['onDelete'])) { 1452 $joinColumnAnnot[] = 'onDelete="' . ($joinColumn['onDelete'] . '"'); 1453 } 1454 1455 if (isset($joinColumn['columnDefinition'])) { 1456 $joinColumnAnnot[] = 'columnDefinition="' . $joinColumn['columnDefinition'] . '"'; 1457 } 1458 1459 return '@' . $this->annotationsPrefix . 'JoinColumn(' . implode(', ', $joinColumnAnnot) . ')'; 1460 } 1461 1462 /** 1463 * @param array $associationMapping 1464 * @param ClassMetadataInfo $metadata 1465 * 1466 * @return string 1467 */ 1468 protected function generateAssociationMappingPropertyDocBlock(array $associationMapping, ClassMetadataInfo $metadata) 1469 { 1470 $lines = array(); 1471 $lines[] = $this->spaces . '/**'; 1472 1473 if ($associationMapping['type'] & ClassMetadataInfo::TO_MANY) { 1474 $lines[] = $this->spaces . ' * @var \Doctrine\Common\Collections\Collection'; 1475 } else { 1476 $lines[] = $this->spaces . ' * @var \\' . ltrim($associationMapping['targetEntity'], '\\'); 1477 } 1478 1479 if ($this->generateAnnotations) { 1480 $lines[] = $this->spaces . ' *'; 1481 1482 if (isset($associationMapping['id']) && $associationMapping['id']) { 1483 $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'Id'; 1484 1485 if ($generatorType = $this->getIdGeneratorTypeString($metadata->generatorType)) { 1486 $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'GeneratedValue(strategy="' . $generatorType . '")'; 1487 } 1488 } 1489 1490 $type = null; 1491 switch ($associationMapping['type']) { 1492 case ClassMetadataInfo::ONE_TO_ONE: 1493 $type = 'OneToOne'; 1494 break; 1495 case ClassMetadataInfo::MANY_TO_ONE: 1496 $type = 'ManyToOne'; 1497 break; 1498 case ClassMetadataInfo::ONE_TO_MANY: 1499 $type = 'OneToMany'; 1500 break; 1501 case ClassMetadataInfo::MANY_TO_MANY: 1502 $type = 'ManyToMany'; 1503 break; 1504 } 1505 $typeOptions = array(); 1506 1507 if (isset($associationMapping['targetEntity'])) { 1508 $typeOptions[] = 'targetEntity="' . $associationMapping['targetEntity'] . '"'; 1509 } 1510 1511 if (isset($associationMapping['inversedBy'])) { 1512 $typeOptions[] = 'inversedBy="' . $associationMapping['inversedBy'] . '"'; 1513 } 1514 1515 if (isset($associationMapping['mappedBy'])) { 1516 $typeOptions[] = 'mappedBy="' . $associationMapping['mappedBy'] . '"'; 1517 } 1518 1519 if ($associationMapping['cascade']) { 1520 $cascades = array(); 1521 1522 if ($associationMapping['isCascadePersist']) $cascades[] = '"persist"'; 1523 if ($associationMapping['isCascadeRemove']) $cascades[] = '"remove"'; 1524 if ($associationMapping['isCascadeDetach']) $cascades[] = '"detach"'; 1525 if ($associationMapping['isCascadeMerge']) $cascades[] = '"merge"'; 1526 if ($associationMapping['isCascadeRefresh']) $cascades[] = '"refresh"'; 1527 1528 if (count($cascades) === 5) { 1529 $cascades = array('"all"'); 1530 } 1531 1532 $typeOptions[] = 'cascade={' . implode(',', $cascades) . '}'; 1533 } 1534 1535 if (isset($associationMapping['orphanRemoval']) && $associationMapping['orphanRemoval']) { 1536 $typeOptions[] = 'orphanRemoval=' . ($associationMapping['orphanRemoval'] ? 'true' : 'false'); 1537 } 1538 1539 if (isset($associationMapping['fetch']) && $associationMapping['fetch'] !== ClassMetadataInfo::FETCH_LAZY) { 1540 $fetchMap = array( 1541 ClassMetadataInfo::FETCH_EXTRA_LAZY => 'EXTRA_LAZY', 1542 ClassMetadataInfo::FETCH_EAGER => 'EAGER', 1543 ); 1544 1545 $typeOptions[] = 'fetch="' . $fetchMap[$associationMapping['fetch']] . '"'; 1546 } 1547 1548 $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . '' . $type . '(' . implode(', ', $typeOptions) . ')'; 1549 1550 if (isset($associationMapping['joinColumns']) && $associationMapping['joinColumns']) { 1551 $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'JoinColumns({'; 1552 1553 $joinColumnsLines = array(); 1554 1555 foreach ($associationMapping['joinColumns'] as $joinColumn) { 1556 if ($joinColumnAnnot = $this->generateJoinColumnAnnotation($joinColumn)) { 1557 $joinColumnsLines[] = $this->spaces . ' * ' . $joinColumnAnnot; 1558 } 1559 } 1560 1561 $lines[] = implode(",\n", $joinColumnsLines); 1562 $lines[] = $this->spaces . ' * })'; 1563 } 1564 1565 if (isset($associationMapping['joinTable']) && $associationMapping['joinTable']) { 1566 $joinTable = array(); 1567 $joinTable[] = 'name="' . $associationMapping['joinTable']['name'] . '"'; 1568 1569 if (isset($associationMapping['joinTable']['schema'])) { 1570 $joinTable[] = 'schema="' . $associationMapping['joinTable']['schema'] . '"'; 1571 } 1572 1573 $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'JoinTable(' . implode(', ', $joinTable) . ','; 1574 $lines[] = $this->spaces . ' * joinColumns={'; 1575 1576 $joinColumnsLines = array(); 1577 1578 foreach ($associationMapping['joinTable']['joinColumns'] as $joinColumn) { 1579 $joinColumnsLines[] = $this->spaces . ' * ' . $this->generateJoinColumnAnnotation($joinColumn); 1580 } 1581 1582 $lines[] = implode(",". PHP_EOL, $joinColumnsLines); 1583 $lines[] = $this->spaces . ' * },'; 1584 $lines[] = $this->spaces . ' * inverseJoinColumns={'; 1585 1586 $inverseJoinColumnsLines = array(); 1587 1588 foreach ($associationMapping['joinTable']['inverseJoinColumns'] as $joinColumn) { 1589 $inverseJoinColumnsLines[] = $this->spaces . ' * ' . $this->generateJoinColumnAnnotation($joinColumn); 1590 } 1591 1592 $lines[] = implode(",". PHP_EOL, $inverseJoinColumnsLines); 1593 $lines[] = $this->spaces . ' * }'; 1594 $lines[] = $this->spaces . ' * )'; 1595 } 1596 1597 if (isset($associationMapping['orderBy'])) { 1598 $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'OrderBy({'; 1599 1600 foreach ($associationMapping['orderBy'] as $name => $direction) { 1601 $lines[] = $this->spaces . ' * "' . $name . '"="' . $direction . '",'; 1602 } 1603 1604 $lines[count($lines) - 1] = substr($lines[count($lines) - 1], 0, strlen($lines[count($lines) - 1]) - 1); 1605 $lines[] = $this->spaces . ' * })'; 1606 } 1607 } 1608 1609 $lines[] = $this->spaces . ' */'; 1610 1611 return implode("\n", $lines); 1612 } 1613 1614 /** 1615 * @param array $fieldMapping 1616 * @param ClassMetadataInfo $metadata 1617 * 1618 * @return string 1619 */ 1620 protected function generateFieldMappingPropertyDocBlock(array $fieldMapping, ClassMetadataInfo $metadata) 1621 { 1622 $lines = array(); 1623 $lines[] = $this->spaces . '/**'; 1624 $lines[] = $this->spaces . ' * @var ' . $this->getType($fieldMapping['type']); 1625 1626 if ($this->generateAnnotations) { 1627 $lines[] = $this->spaces . ' *'; 1628 1629 $column = array(); 1630 if (isset($fieldMapping['columnName'])) { 1631 $column[] = 'name="' . $fieldMapping['columnName'] . '"'; 1632 } 1633 1634 if (isset($fieldMapping['type'])) { 1635 $column[] = 'type="' . $fieldMapping['type'] . '"'; 1636 } 1637 1638 if (isset($fieldMapping['length'])) { 1639 $column[] = 'length=' . $fieldMapping['length']; 1640 } 1641 1642 if (isset($fieldMapping['precision'])) { 1643 $column[] = 'precision=' . $fieldMapping['precision']; 1644 } 1645 1646 if (isset($fieldMapping['scale'])) { 1647 $column[] = 'scale=' . $fieldMapping['scale']; 1648 } 1649 1650 if (isset($fieldMapping['nullable'])) { 1651 $column[] = 'nullable=' . var_export($fieldMapping['nullable'], true); 1652 } 1653 1654 if (isset($fieldMapping['unsigned']) && $fieldMapping['unsigned']) { 1655 $column[] = 'options={"unsigned"=true}'; 1656 } 1657 1658 if (isset($fieldMapping['columnDefinition'])) { 1659 $column[] = 'columnDefinition="' . $fieldMapping['columnDefinition'] . '"'; 1660 } 1661 1662 if (isset($fieldMapping['unique'])) { 1663 $column[] = 'unique=' . var_export($fieldMapping['unique'], true); 1664 } 1665 1666 $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'Column(' . implode(', ', $column) . ')'; 1667 1668 if (isset($fieldMapping['id']) && $fieldMapping['id']) { 1669 $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'Id'; 1670 1671 if ($generatorType = $this->getIdGeneratorTypeString($metadata->generatorType)) { 1672 $lines[] = $this->spaces.' * @' . $this->annotationsPrefix . 'GeneratedValue(strategy="' . $generatorType . '")'; 1673 } 1674 1675 if ($metadata->sequenceGeneratorDefinition) { 1676 $sequenceGenerator = array(); 1677 1678 if (isset($metadata->sequenceGeneratorDefinition['sequenceName'])) { 1679 $sequenceGenerator[] = 'sequenceName="' . $metadata->sequenceGeneratorDefinition['sequenceName'] . '"'; 1680 } 1681 1682 if (isset($metadata->sequenceGeneratorDefinition['allocationSize'])) { 1683 $sequenceGenerator[] = 'allocationSize=' . $metadata->sequenceGeneratorDefinition['allocationSize']; 1684 } 1685 1686 if (isset($metadata->sequenceGeneratorDefinition['initialValue'])) { 1687 $sequenceGenerator[] = 'initialValue=' . $metadata->sequenceGeneratorDefinition['initialValue']; 1688 } 1689 1690 $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'SequenceGenerator(' . implode(', ', $sequenceGenerator) . ')'; 1691 } 1692 } 1693 1694 if (isset($fieldMapping['version']) && $fieldMapping['version']) { 1695 $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'Version'; 1696 } 1697 } 1698 1699 $lines[] = $this->spaces . ' */'; 1700 1701 return implode("\n", $lines); 1702 } 1703 1704 /** 1705 * @param array $embeddedClass 1706 * 1707 * @return string 1708 */ 1709 protected function generateEmbeddedPropertyDocBlock(array $embeddedClass) 1710 { 1711 $lines = array(); 1712 $lines[] = $this->spaces . '/**'; 1713 $lines[] = $this->spaces . ' * @var \\' . ltrim($embeddedClass['class'], '\\'); 1714 1715 if ($this->generateAnnotations) { 1716 $lines[] = $this->spaces . ' *'; 1717 1718 $embedded = array('class="' . $embeddedClass['class'] . '"'); 1719 1720 if (isset($fieldMapping['columnPrefix'])) { 1721 $embedded[] = 'columnPrefix=' . var_export($embeddedClass['columnPrefix'], true); 1722 } 1723 1724 $lines[] = $this->spaces . ' * @' . 1725 $this->annotationsPrefix . 'Embedded(' . implode(', ', $embedded) . ')'; 1726 } 1727 1728 $lines[] = $this->spaces . ' */'; 1729 1730 return implode("\n", $lines); 1731 } 1732 1733 /** 1734 * @param string $code 1735 * @param int $num 1736 * 1737 * @return string 1738 */ 1739 protected function prefixCodeWithSpaces($code, $num = 1) 1740 { 1741 $lines = explode("\n", $code); 1742 1743 foreach ($lines as $key => $value) { 1744 if ( ! empty($value)) { 1745 $lines[$key] = str_repeat($this->spaces, $num) . $lines[$key]; 1746 } 1747 } 1748 1749 return implode("\n", $lines); 1750 } 1751 1752 /** 1753 * @param integer $type The inheritance type used by the class and its subclasses. 1754 * 1755 * @return string The literal string for the inheritance type. 1756 * 1757 * @throws \InvalidArgumentException When the inheritance type does not exist. 1758 */ 1759 protected function getInheritanceTypeString($type) 1760 { 1761 if ( ! isset(static::$inheritanceTypeMap[$type])) { 1762 throw new \InvalidArgumentException(sprintf('Invalid provided InheritanceType: %s', $type)); 1763 } 1764 1765 return static::$inheritanceTypeMap[$type]; 1766 } 1767 1768 /** 1769 * @param integer $type The policy used for change-tracking for the mapped class. 1770 * 1771 * @return string The literal string for the change-tracking type. 1772 * 1773 * @throws \InvalidArgumentException When the change-tracking type does not exist. 1774 */ 1775 protected function getChangeTrackingPolicyString($type) 1776 { 1777 if ( ! isset(static::$changeTrackingPolicyMap[$type])) { 1778 throw new \InvalidArgumentException(sprintf('Invalid provided ChangeTrackingPolicy: %s', $type)); 1779 } 1780 1781 return static::$changeTrackingPolicyMap[$type]; 1782 } 1783 1784 /** 1785 * @param integer $type The generator to use for the mapped class. 1786 * 1787 * @return string The literal string for the generator type. 1788 * 1789 * @throws \InvalidArgumentException When the generator type does not exist. 1790 */ 1791 protected function getIdGeneratorTypeString($type) 1792 { 1793 if ( ! isset(static::$generatorStrategyMap[$type])) { 1794 throw new \InvalidArgumentException(sprintf('Invalid provided IdGeneratorType: %s', $type)); 1795 } 1796 1797 return static::$generatorStrategyMap[$type]; 1798 } 1799 1800 /** 1801 * Exports (nested) option elements. 1802 * 1803 * @param array $options 1804 */ 1805 private function exportTableOptions(array $options) 1806 { 1807 $optionsStr = array(); 1808 1809 foreach($options as $name => $option) { 1810 if (is_array($option)) { 1811 $optionsStr[] = '"' . $name . '"={' . $this->exportTableOptions($option) . '}'; 1812 } else { 1813 $optionsStr[] = '"' . $name . '"="' . (string) $option . '"'; 1814 } 1815 } 1816 1817 return implode(',', $optionsStr); 1818 } 1819} 1820