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