PageRenderTime 34ms CodeModel.GetById 16ms RepoModel.GetById 1ms app.codeStats 1ms

/lib/Doctrine/ORM/Tools/EntityGenerator.php

http://github.com/doctrine/doctrine2
PHP | 1950 lines | 1172 code | 194 blank | 584 comment | 96 complexity | 5a7a78a28ccd34d17d704243b39c89b4 MD5 | raw file
Possible License(s): Unlicense

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. declare(strict_types=1);
  3. namespace Doctrine\ORM\Tools;
  4. use Doctrine\Common\Collections\Collection;
  5. use Doctrine\DBAL\Types\Type;
  6. use Doctrine\DBAL\Types\Types;
  7. use Doctrine\Deprecations\Deprecation;
  8. use Doctrine\Inflector\Inflector;
  9. use Doctrine\Inflector\InflectorFactory;
  10. use Doctrine\ORM\Mapping\ClassMetadataInfo;
  11. use InvalidArgumentException;
  12. use ReflectionClass;
  13. use ReflectionException;
  14. use RuntimeException;
  15. use function array_filter;
  16. use function array_keys;
  17. use function array_map;
  18. use function array_merge;
  19. use function array_sum;
  20. use function array_unique;
  21. use function array_values;
  22. use function basename;
  23. use function chmod;
  24. use function class_exists;
  25. use function copy;
  26. use function count;
  27. use function dirname;
  28. use function explode;
  29. use function file_exists;
  30. use function file_get_contents;
  31. use function file_put_contents;
  32. use function implode;
  33. use function in_array;
  34. use function is_array;
  35. use function is_dir;
  36. use function is_string;
  37. use function ltrim;
  38. use function max;
  39. use function mkdir;
  40. use function sprintf;
  41. use function str_repeat;
  42. use function str_replace;
  43. use function strlen;
  44. use function strpos;
  45. use function strrpos;
  46. use function strtolower;
  47. use function substr;
  48. use function token_get_all;
  49. use function ucfirst;
  50. use function var_export;
  51. use const DIRECTORY_SEPARATOR;
  52. use const PHP_EOL;
  53. use const PHP_VERSION_ID;
  54. use const T_CLASS;
  55. use const T_COMMENT;
  56. use const T_DOC_COMMENT;
  57. use const T_DOUBLE_COLON;
  58. use const T_FUNCTION;
  59. use const T_NAME_FULLY_QUALIFIED;
  60. use const T_NAME_QUALIFIED;
  61. use const T_NAMESPACE;
  62. use const T_NS_SEPARATOR;
  63. use const T_PRIVATE;
  64. use const T_PROTECTED;
  65. use const T_PUBLIC;
  66. use const T_STRING;
  67. use const T_VAR;
  68. use const T_WHITESPACE;
  69. /**
  70. * Generic class used to generate PHP5 entity classes from ClassMetadataInfo instances.
  71. *
  72. * [php]
  73. * $classes = $em->getClassMetadataFactory()->getAllMetadata();
  74. *
  75. * $generator = new \Doctrine\ORM\Tools\EntityGenerator();
  76. * $generator->setGenerateAnnotations(true);
  77. * $generator->setGenerateStubMethods(true);
  78. * $generator->setRegenerateEntityIfExists(false);
  79. * $generator->setUpdateEntityIfExists(true);
  80. * $generator->generate($classes, '/path/to/generate/entities');
  81. *
  82. * @deprecated 2.7 This class is being removed from the ORM and won't have any replacement
  83. *
  84. * @link www.doctrine-project.org
  85. */
  86. class EntityGenerator
  87. {
  88. /**
  89. * Specifies class fields should be protected.
  90. */
  91. public const FIELD_VISIBLE_PROTECTED = 'protected';
  92. /**
  93. * Specifies class fields should be private.
  94. */
  95. public const FIELD_VISIBLE_PRIVATE = 'private';
  96. /** @var bool */
  97. protected $backupExisting = true;
  98. /**
  99. * The extension to use for written php files.
  100. *
  101. * @var string
  102. */
  103. protected $extension = '.php';
  104. /**
  105. * Whether or not the current ClassMetadataInfo instance is new or old.
  106. *
  107. * @var bool
  108. */
  109. protected $isNew = true;
  110. /** @var mixed[] */
  111. protected $staticReflection = [];
  112. /**
  113. * Number of spaces to use for indention in generated code.
  114. *
  115. * @var int
  116. */
  117. protected $numSpaces = 4;
  118. /**
  119. * The actual spaces to use for indention.
  120. *
  121. * @var string
  122. */
  123. protected $spaces = ' ';
  124. /**
  125. * The class all generated entities should extend.
  126. *
  127. * @var string
  128. */
  129. protected $classToExtend;
  130. /**
  131. * Whether or not to generation annotations.
  132. *
  133. * @var bool
  134. */
  135. protected $generateAnnotations = false;
  136. /** @var string */
  137. protected $annotationsPrefix = '';
  138. /**
  139. * Whether or not to generate sub methods.
  140. *
  141. * @var bool
  142. */
  143. protected $generateEntityStubMethods = false;
  144. /**
  145. * Whether or not to update the entity class if it exists already.
  146. *
  147. * @var bool
  148. */
  149. protected $updateEntityIfExists = false;
  150. /**
  151. * Whether or not to re-generate entity class if it exists already.
  152. *
  153. * @var bool
  154. */
  155. protected $regenerateEntityIfExists = false;
  156. /**
  157. * Visibility of the field
  158. *
  159. * @var string
  160. */
  161. protected $fieldVisibility = 'private';
  162. /**
  163. * Whether or not to make generated embeddables immutable.
  164. *
  165. * @var bool
  166. */
  167. protected $embeddablesImmutable = false;
  168. /**
  169. * Hash-map for handle types.
  170. *
  171. * @psalm-var array<Types::*|'json_array', string>
  172. */
  173. protected $typeAlias = [
  174. Types::DATETIMETZ_MUTABLE => '\DateTime',
  175. Types::DATETIME_MUTABLE => '\DateTime',
  176. Types::DATE_MUTABLE => '\DateTime',
  177. Types::TIME_MUTABLE => '\DateTime',
  178. Types::OBJECT => '\stdClass',
  179. Types::INTEGER => 'int',
  180. Types::BIGINT => 'int',
  181. Types::SMALLINT => 'int',
  182. Types::TEXT => 'string',
  183. Types::BLOB => 'string',
  184. Types::DECIMAL => 'string',
  185. Types::GUID => 'string',
  186. 'json_array' => 'array',
  187. Types::JSON => 'array',
  188. Types::SIMPLE_ARRAY => 'array',
  189. Types::BOOLEAN => 'bool',
  190. ];
  191. /**
  192. * Hash-map to handle generator types string.
  193. *
  194. * @psalm-var array<ClassMetadataInfo::GENERATOR_TYPE_*, string>
  195. */
  196. protected static $generatorStrategyMap = [
  197. ClassMetadataInfo::GENERATOR_TYPE_AUTO => 'AUTO',
  198. ClassMetadataInfo::GENERATOR_TYPE_SEQUENCE => 'SEQUENCE',
  199. ClassMetadataInfo::GENERATOR_TYPE_IDENTITY => 'IDENTITY',
  200. ClassMetadataInfo::GENERATOR_TYPE_NONE => 'NONE',
  201. ClassMetadataInfo::GENERATOR_TYPE_UUID => 'UUID',
  202. ClassMetadataInfo::GENERATOR_TYPE_CUSTOM => 'CUSTOM',
  203. ];
  204. /**
  205. * Hash-map to handle the change tracking policy string.
  206. *
  207. * @psalm-var array<ClassMetadataInfo::CHANGETRACKING_*, string>
  208. */
  209. protected static $changeTrackingPolicyMap = [
  210. ClassMetadataInfo::CHANGETRACKING_DEFERRED_IMPLICIT => 'DEFERRED_IMPLICIT',
  211. ClassMetadataInfo::CHANGETRACKING_DEFERRED_EXPLICIT => 'DEFERRED_EXPLICIT',
  212. ClassMetadataInfo::CHANGETRACKING_NOTIFY => 'NOTIFY',
  213. ];
  214. /**
  215. * Hash-map to handle the inheritance type string.
  216. *
  217. * @psalm-var array<ClassMetadataInfo::INHERITANCE_TYPE_*, string>
  218. */
  219. protected static $inheritanceTypeMap = [
  220. ClassMetadataInfo::INHERITANCE_TYPE_NONE => 'NONE',
  221. ClassMetadataInfo::INHERITANCE_TYPE_JOINED => 'JOINED',
  222. ClassMetadataInfo::INHERITANCE_TYPE_SINGLE_TABLE => 'SINGLE_TABLE',
  223. ClassMetadataInfo::INHERITANCE_TYPE_TABLE_PER_CLASS => 'TABLE_PER_CLASS',
  224. ];
  225. /** @var string */
  226. protected static $classTemplate =
  227. '<?php
  228. <namespace>
  229. <useStatement>
  230. <entityAnnotation>
  231. <entityClassName>
  232. {
  233. <entityBody>
  234. }
  235. ';
  236. /** @var string */
  237. protected static $getMethodTemplate =
  238. '/**
  239. * <description>
  240. *
  241. * @return <variableType>
  242. */
  243. public function <methodName>()
  244. {
  245. <spaces>return $this-><fieldName>;
  246. }';
  247. /** @var string */
  248. protected static $setMethodTemplate =
  249. '/**
  250. * <description>
  251. *
  252. * @param <variableType> $<variableName>
  253. *
  254. * @return <entity>
  255. */
  256. public function <methodName>(<methodTypeHint>$<variableName><variableDefault>)
  257. {
  258. <spaces>$this-><fieldName> = $<variableName>;
  259. <spaces>return $this;
  260. }';
  261. /** @var string */
  262. protected static $addMethodTemplate =
  263. '/**
  264. * <description>
  265. *
  266. * @param <variableType> $<variableName>
  267. *
  268. * @return <entity>
  269. */
  270. public function <methodName>(<methodTypeHint>$<variableName>)
  271. {
  272. <spaces>$this-><fieldName>[] = $<variableName>;
  273. <spaces>return $this;
  274. }';
  275. /** @var string */
  276. protected static $removeMethodTemplate =
  277. '/**
  278. * <description>
  279. *
  280. * @param <variableType> $<variableName>
  281. *
  282. * @return boolean TRUE if this collection contained the specified element, FALSE otherwise.
  283. */
  284. public function <methodName>(<methodTypeHint>$<variableName>)
  285. {
  286. <spaces>return $this-><fieldName>->removeElement($<variableName>);
  287. }';
  288. /** @var string */
  289. protected static $lifecycleCallbackMethodTemplate =
  290. '/**
  291. * @<name>
  292. */
  293. public function <methodName>()
  294. {
  295. <spaces>// Add your code here
  296. }';
  297. /** @var string */
  298. protected static $constructorMethodTemplate =
  299. '/**
  300. * Constructor
  301. */
  302. public function __construct()
  303. {
  304. <spaces><collections>
  305. }
  306. ';
  307. /** @var string */
  308. protected static $embeddableConstructorMethodTemplate =
  309. '/**
  310. * Constructor
  311. *
  312. * <paramTags>
  313. */
  314. public function __construct(<params>)
  315. {
  316. <spaces><fields>
  317. }
  318. ';
  319. /** @var Inflector */
  320. protected $inflector;
  321. public function __construct()
  322. {
  323. Deprecation::trigger(
  324. 'doctrine/orm',
  325. 'https://github.com/doctrine/orm/issues/8458',
  326. '%s is deprecated with no replacement',
  327. self::class
  328. );
  329. $this->annotationsPrefix = 'ORM\\';
  330. $this->inflector = InflectorFactory::create()->build();
  331. }
  332. /**
  333. * Generates and writes entity classes for the given array of ClassMetadataInfo instances.
  334. *
  335. * @param string $outputDirectory
  336. * @psalm-param list<ClassMetadataInfo> $metadatas
  337. *
  338. * @return void
  339. */
  340. public function generate(array $metadatas, $outputDirectory)
  341. {
  342. foreach ($metadatas as $metadata) {
  343. $this->writeEntityClass($metadata, $outputDirectory);
  344. }
  345. }
  346. /**
  347. * Generates and writes entity class to disk for the given ClassMetadataInfo instance.
  348. *
  349. * @param string $outputDirectory
  350. *
  351. * @return void
  352. *
  353. * @throws RuntimeException
  354. */
  355. public function writeEntityClass(ClassMetadataInfo $metadata, $outputDirectory)
  356. {
  357. $path = $outputDirectory . '/' . str_replace('\\', DIRECTORY_SEPARATOR, $metadata->name) . $this->extension;
  358. $dir = dirname($path);
  359. if (! is_dir($dir)) {
  360. mkdir($dir, 0775, true);
  361. }
  362. $this->isNew = ! file_exists($path) || $this->regenerateEntityIfExists;
  363. if (! $this->isNew) {
  364. $this->parseTokensInEntityFile(file_get_contents($path));
  365. } else {
  366. $this->staticReflection[$metadata->name] = ['properties' => [], 'methods' => []];
  367. }
  368. if ($this->backupExisting && file_exists($path)) {
  369. $backupPath = dirname($path) . DIRECTORY_SEPARATOR . basename($path) . '~';
  370. if (! copy($path, $backupPath)) {
  371. throw new RuntimeException('Attempt to backup overwritten entity file but copy operation failed.');
  372. }
  373. }
  374. // If entity doesn't exist or we're re-generating the entities entirely
  375. if ($this->isNew) {
  376. file_put_contents($path, $this->generateEntityClass($metadata));
  377. // If entity exists and we're allowed to update the entity class
  378. } elseif ($this->updateEntityIfExists) {
  379. file_put_contents($path, $this->generateUpdatedEntityClass($metadata, $path));
  380. }
  381. chmod($path, 0664);
  382. }
  383. /**
  384. * Generates a PHP5 Doctrine 2 entity class from the given ClassMetadataInfo instance.
  385. *
  386. * @return string
  387. */
  388. public function generateEntityClass(ClassMetadataInfo $metadata)
  389. {
  390. $placeHolders = [
  391. '<namespace>',
  392. '<useStatement>',
  393. '<entityAnnotation>',
  394. '<entityClassName>',
  395. '<entityBody>',
  396. ];
  397. $replacements = [
  398. $this->generateEntityNamespace($metadata),
  399. $this->generateEntityUse(),
  400. $this->generateEntityDocBlock($metadata),
  401. $this->generateEntityClassName($metadata),
  402. $this->generateEntityBody($metadata),
  403. ];
  404. $code = str_replace($placeHolders, $replacements, static::$classTemplate);
  405. return str_replace('<spaces>', $this->spaces, $code);
  406. }
  407. /**
  408. * Generates the updated code for the given ClassMetadataInfo and entity at path.
  409. *
  410. * @param string $path
  411. *
  412. * @return string
  413. */
  414. public function generateUpdatedEntityClass(ClassMetadataInfo $metadata, $path)
  415. {
  416. $currentCode = file_get_contents($path);
  417. $body = $this->generateEntityBody($metadata);
  418. $body = str_replace('<spaces>', $this->spaces, $body);
  419. $last = strrpos($currentCode, '}');
  420. return substr($currentCode, 0, $last) . $body . ($body ? "\n" : '') . "}\n";
  421. }
  422. /**
  423. * Sets the number of spaces the exported class should have.
  424. *
  425. * @param int $numSpaces
  426. *
  427. * @return void
  428. */
  429. public function setNumSpaces($numSpaces)
  430. {
  431. $this->spaces = str_repeat(' ', $numSpaces);
  432. $this->numSpaces = $numSpaces;
  433. }
  434. /**
  435. * Sets the extension to use when writing php files to disk.
  436. *
  437. * @param string $extension
  438. *
  439. * @return void
  440. */
  441. public function setExtension($extension)
  442. {
  443. $this->extension = $extension;
  444. }
  445. /**
  446. * Sets the name of the class the generated classes should extend from.
  447. *
  448. * @param string $classToExtend
  449. *
  450. * @return void
  451. */
  452. public function setClassToExtend($classToExtend)
  453. {
  454. $this->classToExtend = $classToExtend;
  455. }
  456. /**
  457. * Sets whether or not to generate annotations for the entity.
  458. *
  459. * @param bool $bool
  460. *
  461. * @return void
  462. */
  463. public function setGenerateAnnotations($bool)
  464. {
  465. $this->generateAnnotations = $bool;
  466. }
  467. /**
  468. * Sets the class fields visibility for the entity (can either be private or protected).
  469. *
  470. * @param string $visibility
  471. * @psalm-param self::FIELD_VISIBLE_*
  472. *
  473. * @return void
  474. *
  475. * @throws InvalidArgumentException
  476. */
  477. public function setFieldVisibility($visibility)
  478. {
  479. if ($visibility !== self::FIELD_VISIBLE_PRIVATE && $visibility !== self::FIELD_VISIBLE_PROTECTED) {
  480. throw new InvalidArgumentException('Invalid provided visibility (only private and protected are allowed): ' . $visibility);
  481. }
  482. $this->fieldVisibility = $visibility;
  483. }
  484. /**
  485. * Sets whether or not to generate immutable embeddables.
  486. *
  487. * @param bool $embeddablesImmutable
  488. *
  489. * @return void
  490. */
  491. public function setEmbeddablesImmutable($embeddablesImmutable)
  492. {
  493. $this->embeddablesImmutable = (bool) $embeddablesImmutable;
  494. }
  495. /**
  496. * Sets an annotation prefix.
  497. *
  498. * @param string $prefix
  499. *
  500. * @return void
  501. */
  502. public function setAnnotationPrefix($prefix)
  503. {
  504. $this->annotationsPrefix = $prefix;
  505. }
  506. /**
  507. * Sets whether or not to try and update the entity if it already exists.
  508. *
  509. * @param bool $bool
  510. *
  511. * @return void
  512. */
  513. public function setUpdateEntityIfExists($bool)
  514. {
  515. $this->updateEntityIfExists = $bool;
  516. }
  517. /**
  518. * Sets whether or not to regenerate the entity if it exists.
  519. *
  520. * @param bool $bool
  521. *
  522. * @return void
  523. */
  524. public function setRegenerateEntityIfExists($bool)
  525. {
  526. $this->regenerateEntityIfExists = $bool;
  527. }
  528. /**
  529. * Sets whether or not to generate stub methods for the entity.
  530. *
  531. * @param bool $bool
  532. *
  533. * @return void
  534. */
  535. public function setGenerateStubMethods($bool)
  536. {
  537. $this->generateEntityStubMethods = $bool;
  538. }
  539. /**
  540. * Should an existing entity be backed up if it already exists?
  541. *
  542. * @param bool $bool
  543. *
  544. * @return void
  545. */
  546. public function setBackupExisting($bool)
  547. {
  548. $this->backupExisting = $bool;
  549. }
  550. public function setInflector(Inflector $inflector): void
  551. {
  552. $this->inflector = $inflector;
  553. }
  554. /**
  555. * @param string $type
  556. *
  557. * @return string
  558. */
  559. protected function getType($type)
  560. {
  561. if (isset($this->typeAlias[$type])) {
  562. return $this->typeAlias[$type];
  563. }
  564. return $type;
  565. }
  566. /**
  567. * @return string
  568. */
  569. protected function generateEntityNamespace(ClassMetadataInfo $metadata)
  570. {
  571. if (! $this->hasNamespace($metadata)) {
  572. return '';
  573. }
  574. return 'namespace ' . $this->getNamespace($metadata) . ';';
  575. }
  576. /**
  577. * @return string
  578. */
  579. protected function generateEntityUse()
  580. {
  581. if (! $this->generateAnnotations) {
  582. return '';
  583. }
  584. return "\n" . 'use Doctrine\ORM\Mapping as ORM;' . "\n";
  585. }
  586. /**
  587. * @return string
  588. */
  589. protected function generateEntityClassName(ClassMetadataInfo $metadata)
  590. {
  591. return 'class ' . $this->getClassName($metadata) .
  592. ($this->extendsClass() ? ' extends ' . $this->getClassToExtendName() : null);
  593. }
  594. /**
  595. * @return string
  596. */
  597. protected function generateEntityBody(ClassMetadataInfo $metadata)
  598. {
  599. $fieldMappingProperties = $this->generateEntityFieldMappingProperties($metadata);
  600. $embeddedProperties = $this->generateEntityEmbeddedProperties($metadata);
  601. $associationMappingProperties = $this->generateEntityAssociationMappingProperties($metadata);
  602. $stubMethods = $this->generateEntityStubMethods ? $this->generateEntityStubMethods($metadata) : null;
  603. $lifecycleCallbackMethods = $this->generateEntityLifecycleCallbackMethods($metadata);
  604. $code = [];
  605. if ($fieldMappingProperties) {
  606. $code[] = $fieldMappingProperties;
  607. }
  608. if ($embeddedProperties) {
  609. $code[] = $embeddedProperties;
  610. }
  611. if ($associationMappingProperties) {
  612. $code[] = $associationMappingProperties;
  613. }
  614. $code[] = $this->generateEntityConstructor($metadata);
  615. if ($stubMethods) {
  616. $code[] = $stubMethods;
  617. }
  618. if ($lifecycleCallbackMethods) {
  619. $code[] = $lifecycleCallbackMethods;
  620. }
  621. return implode("\n", $code);
  622. }
  623. /**
  624. * @return string
  625. */
  626. protected function generateEntityConstructor(ClassMetadataInfo $metadata)
  627. {
  628. if ($this->hasMethod('__construct', $metadata)) {
  629. return '';
  630. }
  631. if ($metadata->isEmbeddedClass && $this->embeddablesImmutable) {
  632. return $this->generateEmbeddableConstructor($metadata);
  633. }
  634. $collections = [];
  635. foreach ($metadata->associationMappings as $mapping) {
  636. if ($mapping['type'] & ClassMetadataInfo::TO_MANY) {
  637. $collections[] = '$this->' . $mapping['fieldName'] . ' = new \Doctrine\Common\Collections\ArrayCollection();';
  638. }
  639. }
  640. if ($collections) {
  641. return $this->prefixCodeWithSpaces(str_replace('<collections>', implode("\n" . $this->spaces, $collections), static::$constructorMethodTemplate));
  642. }
  643. return '';
  644. }
  645. private function generateEmbeddableConstructor(ClassMetadataInfo $metadata): string
  646. {
  647. $paramTypes = [];
  648. $paramVariables = [];
  649. $params = [];
  650. $fields = [];
  651. // Resort fields to put optional fields at the end of the method signature.
  652. $requiredFields = [];
  653. $optionalFields = [];
  654. foreach ($metadata->fieldMappings as $fieldMapping) {
  655. if (empty($fieldMapping['nullable'])) {
  656. $requiredFields[] = $fieldMapping;
  657. continue;
  658. }
  659. $optionalFields[] = $fieldMapping;
  660. }
  661. $fieldMappings = array_merge($requiredFields, $optionalFields);
  662. foreach ($metadata->embeddedClasses as $fieldName => $embeddedClass) {
  663. $paramType = '\\' . ltrim($embeddedClass['class'], '\\');
  664. $paramVariable = '$' . $fieldName;
  665. $paramTypes[] = $paramType;
  666. $paramVariables[] = $paramVariable;
  667. $params[] = $paramType . ' ' . $paramVariable;
  668. $fields[] = '$this->' . $fieldName . ' = ' . $paramVariable . ';';
  669. }
  670. foreach ($fieldMappings as $fieldMapping) {
  671. if (isset($fieldMapping['declaredField'], $metadata->embeddedClasses[$fieldMapping['declaredField']])) {
  672. continue;
  673. }
  674. $paramTypes[] = $this->getType($fieldMapping['type']) . (! empty($fieldMapping['nullable']) ? '|null' : '');
  675. $param = '$' . $fieldMapping['fieldName'];
  676. $paramVariables[] = $param;
  677. if ($fieldMapping['type'] === 'datetime') {
  678. $param = $this->getType($fieldMapping['type']) . ' ' . $param;
  679. }
  680. if (! empty($fieldMapping['nullable'])) {
  681. $param .= ' = null';
  682. }
  683. $params[] = $param;
  684. $fields[] = '$this->' . $fieldMapping['fieldName'] . ' = $' . $fieldMapping['fieldName'] . ';';
  685. }
  686. $maxParamTypeLength = max(array_map('strlen', $paramTypes));
  687. $paramTags = array_map(
  688. static function ($type, $variable) use ($maxParamTypeLength) {
  689. return '@param ' . $type . str_repeat(' ', $maxParamTypeLength - strlen($type) + 1) . $variable;
  690. },
  691. $paramTypes,
  692. $paramVariables
  693. );
  694. // Generate multi line constructor if the signature exceeds 120 characters.
  695. if (array_sum(array_map('strlen', $params)) + count($params) * 2 + 29 > 120) {
  696. $delimiter = "\n" . $this->spaces;
  697. $params = $delimiter . implode(',' . $delimiter, $params) . "\n";
  698. } else {
  699. $params = implode(', ', $params);
  700. }
  701. $replacements = [
  702. '<paramTags>' => implode("\n * ", $paramTags),
  703. '<params>' => $params,
  704. '<fields>' => implode("\n" . $this->spaces, $fields),
  705. ];
  706. $constructor = str_replace(
  707. array_keys($replacements),
  708. array_values($replacements),
  709. static::$embeddableConstructorMethodTemplate
  710. );
  711. return $this->prefixCodeWithSpaces($constructor);
  712. }
  713. /**
  714. * @param string $src
  715. *
  716. * @return void
  717. *
  718. * @todo this won't work if there is a namespace in brackets and a class outside of it.
  719. * @psalm-suppress UndefinedConstant
  720. */
  721. protected function parseTokensInEntityFile($src)
  722. {
  723. $tokens = token_get_all($src);
  724. $tokensCount = count($tokens);
  725. $lastSeenNamespace = '';
  726. $lastSeenClass = false;
  727. $inNamespace = false;
  728. $inClass = false;
  729. for ($i = 0; $i < $tokensCount; $i++) {
  730. $token = $tokens[$i];
  731. if (in_array($token[0], [T_WHITESPACE, T_COMMENT, T_DOC_COMMENT], true)) {
  732. continue;
  733. }
  734. if ($inNamespace) {
  735. if (in_array($token[0], [T_NS_SEPARATOR, T_STRING], true)) {
  736. $lastSeenNamespace .= $token[1];
  737. } elseif (PHP_VERSION_ID >= 80000 && ($token[0] === T_NAME_QUALIFIED || $token[0] === T_NAME_FULLY_QUALIFIED)) {
  738. $lastSeenNamespace .= $token[1];
  739. } elseif (is_string($token) && in_array($token, [';', '{'], true)) {
  740. $inNamespace = false;
  741. }
  742. }
  743. if ($inClass) {
  744. $inClass = false;
  745. $lastSeenClass = $lastSeenNamespace . ($lastSeenNamespace ? '\\' : '') . $token[1];
  746. $this->staticReflection[$lastSeenClass]['properties'] = [];
  747. $this->staticReflection[$lastSeenClass]['methods'] = [];
  748. }
  749. if ($token[0] === T_NAMESPACE) {
  750. $lastSeenNamespace = '';
  751. $inNamespace = true;
  752. } elseif ($token[0] === T_CLASS && $tokens[$i - 1][0] !== T_DOUBLE_COLON) {
  753. $inClass = true;
  754. } elseif ($token[0] === T_FUNCTION) {
  755. if ($tokens[$i + 2][0] === T_STRING) {
  756. $this->staticReflection[$lastSeenClass]['methods'][] = strtolower($tokens[$i + 2][1]);
  757. } elseif ($tokens[$i + 2] === '&' && $tokens[$i + 3][0] === T_STRING) {
  758. $this->staticReflection[$lastSeenClass]['methods'][] = strtolower($tokens[$i + 3][1]);
  759. }
  760. } elseif (in_array($token[0], [T_VAR, T_PUBLIC, T_PRIVATE, T_PROTECTED], true) && $tokens[$i + 2][0] !== T_FUNCTION) {
  761. $this->staticReflection[$lastSeenClass]['properties'][] = substr($tokens[$i + 2][1], 1);
  762. }
  763. }
  764. }
  765. /**
  766. * @param string $property
  767. *
  768. * @return bool
  769. */
  770. protected function hasProperty($property, ClassMetadataInfo $metadata)
  771. {
  772. if ($this->extendsClass() || (! $this->isNew && class_exists($metadata->name))) {
  773. // don't generate property if its already on the base class.
  774. $reflClass = new ReflectionClass($this->getClassToExtend() ?: $metadata->name);
  775. if ($reflClass->hasProperty($property)) {
  776. return true;
  777. }
  778. }
  779. // check traits for existing property
  780. foreach ($this->getTraits($metadata) as $trait) {
  781. if ($trait->hasProperty($property)) {
  782. return true;
  783. }
  784. }
  785. return isset($this->staticReflection[$metadata->name]) &&
  786. in_array($property, $this->staticReflection[$metadata->name]['properties'], true);
  787. }
  788. /**
  789. * @param string $method
  790. *
  791. * @return bool
  792. */
  793. protected function hasMethod($method, ClassMetadataInfo $metadata)
  794. {
  795. if ($this->extendsClass() || (! $this->isNew && class_exists($metadata->name))) {
  796. // don't generate method if its already on the base class.
  797. $reflClass = new ReflectionClass($this->getClassToExtend() ?: $metadata->name);
  798. if ($reflClass->hasMethod($method)) {
  799. return true;
  800. }
  801. }
  802. // check traits for existing method
  803. foreach ($this->getTraits($metadata) as $trait) {
  804. if ($trait->hasMethod($method)) {
  805. return true;
  806. }
  807. }
  808. return isset($this->staticReflection[$metadata->name]) &&
  809. in_array(strtolower($method), $this->staticReflection[$metadata->name]['methods'], true);
  810. }
  811. /**
  812. * @return ReflectionClass[]
  813. * @psalm-return array<trait-string, ReflectionClass<object>>
  814. *
  815. * @throws ReflectionException
  816. */
  817. protected function getTraits(ClassMetadataInfo $metadata)
  818. {
  819. if (! ($metadata->reflClass !== null || class_exists($metadata->name))) {
  820. return [];
  821. }
  822. $reflClass = $metadata->reflClass ?? new ReflectionClass($metadata->name);
  823. $traits = [];
  824. while ($reflClass !== false) {
  825. $traits = array_merge($traits, $reflClass->getTraits());
  826. $reflClass = $reflClass->getParentClass();
  827. }
  828. return $traits;
  829. }
  830. /**
  831. * @return bool
  832. */
  833. protected function hasNamespace(ClassMetadataInfo $metadata)
  834. {
  835. return (bool) strpos($metadata->name, '\\');
  836. }
  837. /**
  838. * @return bool
  839. */
  840. protected function extendsClass()
  841. {
  842. return (bool) $this->classToExtend;
  843. }
  844. /**
  845. * @return string
  846. */
  847. protected function getClassToExtend()
  848. {
  849. return $this->classToExtend;
  850. }
  851. /**
  852. * @return string
  853. */
  854. protected function getClassToExtendName()
  855. {
  856. $refl = new ReflectionClass($this->getClassToExtend());
  857. return '\\' . $refl->getName();
  858. }
  859. /**
  860. * @return string
  861. */
  862. protected function getClassName(ClassMetadataInfo $metadata)
  863. {
  864. return ($pos = strrpos($metadata->name, '\\'))
  865. ? substr($metadata->name, $pos + 1, strlen($metadata->name)) : $metadata->name;
  866. }
  867. /**
  868. * @return string
  869. */
  870. protected function getNamespace(ClassMetadataInfo $metadata)
  871. {
  872. return substr($metadata->name, 0, strrpos($metadata->name, '\\'));
  873. }
  874. /**
  875. * @return string
  876. */
  877. protected function generateEntityDocBlock(ClassMetadataInfo $metadata)
  878. {
  879. $lines = [];
  880. $lines[] = '/**';
  881. $lines[] = ' * ' . $this->getClassName($metadata);
  882. if ($this->generateAnnotations) {
  883. $lines[] = ' *';
  884. $methods = [
  885. 'generateTableAnnotation',
  886. 'generateInheritanceAnnotation',
  887. 'generateDiscriminatorColumnAnnotation',
  888. 'generateDiscriminatorMapAnnotation',
  889. 'generateEntityAnnotation',
  890. 'generateEntityListenerAnnotation',
  891. ];
  892. foreach ($methods as $method) {
  893. $code = $this->$method($metadata);
  894. if ($code) {
  895. $lines[] = ' * ' . $code;
  896. }
  897. }
  898. if (isset($metadata->lifecycleCallbacks) && $metadata->lifecycleCallbacks) {
  899. $lines[] = ' * @' . $this->annotationsPrefix . 'HasLifecycleCallbacks';
  900. }
  901. }
  902. $lines[] = ' */';
  903. return implode("\n", $lines);
  904. }
  905. /**
  906. * @return string
  907. */
  908. protected function generateEntityAnnotation(ClassMetadataInfo $metadata)
  909. {
  910. $prefix = '@' . $this->annotationsPrefix;
  911. if ($metadata->isEmbeddedClass) {
  912. return $prefix . 'Embeddable';
  913. }
  914. $customRepository = $metadata->customRepositoryClassName
  915. ? '(repositoryClass="' . $metadata->customRepositoryClassName . '")'
  916. : '';
  917. return $prefix . ($metadata->isMappedSuperclass ? 'MappedSuperclass' : 'Entity') . $customRepository;
  918. }
  919. /**
  920. * @return string
  921. */
  922. protected function generateTableAnnotation(ClassMetadataInfo $metadata)
  923. {
  924. if ($metadata->isEmbeddedClass) {
  925. return '';
  926. }
  927. $table = [];
  928. if (isset($metadata->table['schema'])) {
  929. $table[] = 'schema="' . $metadata->table['schema'] . '"';
  930. }
  931. if (isset($metadata->table['name'])) {
  932. $table[] = 'name="' . $metadata->table['name'] . '"';
  933. }
  934. if (isset($metadata->table['options']) && $metadata->table['options']) {
  935. $table[] = 'options={' . $this->exportTableOptions((array) $metadata->table['options']) . '}';
  936. }
  937. if (isset($metadata->table['uniqueConstraints']) && $metadata->table['uniqueConstraints']) {
  938. $constraints = $this->generateTableConstraints('UniqueConstraint', $metadata->table['uniqueConstraints']);
  939. $table[] = 'uniqueConstraints={' . $constraints . '}';
  940. }
  941. if (isset($metadata->table['indexes']) && $metadata->table['indexes']) {
  942. $constraints = $this->generateTableConstraints('Index', $metadata->table['indexes']);
  943. $table[] = 'indexes={' . $constraints . '}';
  944. }
  945. return '@' . $this->annotationsPrefix . 'Table(' . implode(', ', $table) . ')';
  946. }
  947. /**
  948. * @param string $constraintName
  949. * @psalm-param array<string, array<string, mixed>> $constraints
  950. *
  951. * @return string
  952. */
  953. protected function generateTableConstraints($constraintName, array $constraints)
  954. {
  955. $annotations = [];
  956. foreach ($constraints as $name => $constraint) {
  957. $columns = [];
  958. foreach ($constraint['columns'] as $column) {
  959. $columns[] = '"' . $column . '"';
  960. }
  961. $annotations[] = '@' . $this->annotationsPrefix . $constraintName . '(name="' . $name . '", columns={' . implode(', ', $columns) . '})';
  962. }
  963. return implode(', ', $annotations);
  964. }
  965. /**
  966. * @return string
  967. */
  968. protected function generateInheritanceAnnotation(ClassMetadataInfo $metadata)
  969. {
  970. if ($metadata->inheritanceType === ClassMetadataInfo::INHERITANCE_TYPE_NONE) {
  971. return '';
  972. }
  973. return '@' . $this->annotationsPrefix . 'InheritanceType("' . $this->getInheritanceTypeString($metadata->inheritanceType) . '")';
  974. }
  975. /**
  976. * @return string
  977. */
  978. protected function generateDiscriminatorColumnAnnotation(ClassMetadataInfo $metadata)
  979. {
  980. if ($metadata->inheritanceType === ClassMetadataInfo::INHERITANCE_TYPE_NONE) {
  981. return '';
  982. }
  983. $discrColumn = $metadata->discriminatorColumn;
  984. $columnDefinition = 'name="' . $discrColumn['name']
  985. . '", type="' . $discrColumn['type']
  986. . '", length=' . $discrColumn['length'];
  987. return '@' . $this->annotationsPrefix . 'DiscriminatorColumn(' . $columnDefinition . ')';
  988. }
  989. /**
  990. * @return string|null
  991. */
  992. protected function generateDiscriminatorMapAnnotation(ClassMetadataInfo $metadata)
  993. {
  994. if ($metadata->inheritanceType === ClassMetadataInfo::INHERITANCE_TYPE_NONE) {
  995. return null;
  996. }
  997. $inheritanceClassMap = [];
  998. foreach ($metadata->discriminatorMap as $type => $class) {
  999. $inheritanceClassMap[] = '"' . $type . '" = "' . $class . '"';
  1000. }
  1001. return '@' . $this->annotationsPrefix . 'DiscriminatorMap({' . implode(', ', $inheritanceClassMap) . '})';
  1002. }
  1003. /**
  1004. * @return string
  1005. */
  1006. protected function generateEntityStubMethods(ClassMetadataInfo $metadata)
  1007. {
  1008. $methods = [];
  1009. foreach ($metadata->fieldMappings as $fieldMapping) {
  1010. if (isset($fieldMapping['declaredField'], $metadata->embeddedClasses[$fieldMapping['declaredField']])) {
  1011. continue;
  1012. }
  1013. $nullableField = $this->nullableFieldExpression($fieldMapping);
  1014. if (
  1015. (! $metadata->isEmbeddedClass || ! $this->embeddablesImmutable)
  1016. && (! isset($fieldMapping['id']) || ! $fieldMapping['id'] || $metadata->generatorType === ClassMetadataInfo::GENERATOR_TYPE_NONE)
  1017. ) {
  1018. $methods[] = $this->generateEntityStubMethod(
  1019. $metadata,
  1020. 'set',
  1021. $fieldMapping['fieldName'],
  1022. $fieldMapping['type'],
  1023. $nullableField
  1024. );
  1025. }
  1026. $methods[] = $this->generateEntityStubMethod(
  1027. $metadata,
  1028. 'get',
  1029. $fieldMapping['fieldName'],
  1030. $fieldMapping['type'],
  1031. $nullableField
  1032. );
  1033. }
  1034. foreach ($metadata->embeddedClasses as $fieldName => $embeddedClass) {
  1035. if (isset($embeddedClass['declaredField'])) {
  1036. continue;
  1037. }
  1038. if (! $metadata->isEmbeddedClass || ! $this->embeddablesImmutable) {
  1039. $methods[] = $this->generateEntityStubMethod(
  1040. $metadata,
  1041. 'set',
  1042. $fieldName,
  1043. $embeddedClass['class']
  1044. );
  1045. }
  1046. $methods[] = $this->generateEntityStubMethod(
  1047. $metadata,
  1048. 'get',
  1049. $fieldName,
  1050. $embeddedClass['class']
  1051. );
  1052. }
  1053. foreach ($metadata->associationMappings as $associationMapping) {
  1054. if ($associationMapping['type'] & ClassMetadataInfo::TO_ONE) {
  1055. $nullable = $this->isAssociationIsNullable($associationMapping) ? 'null' : null;
  1056. $methods[] = $this->generateEntityStubMethod(
  1057. $metadata,
  1058. 'set',
  1059. $associationMapping['fieldName'],
  1060. $associationMapping['targetEntity'],
  1061. $nullable
  1062. );
  1063. $methods[] = $this->generateEntityStubMethod(
  1064. $metadata,
  1065. 'get',
  1066. $associationMapping['fieldName'],
  1067. $associationMapping['targetEntity'],
  1068. $nullable
  1069. );
  1070. } elseif ($associationMapping['type'] & ClassMetadataInfo::TO_MANY) {
  1071. $methods[] = $this->generateEntityStubMethod(
  1072. $metadata,
  1073. 'add',
  1074. $associationMapping['fieldName'],
  1075. $associationMapping['targetEntity']
  1076. );
  1077. $methods[] = $this->generateEntityStubMethod(
  1078. $metadata,
  1079. 'remove',
  1080. $associationMapping['fieldName'],
  1081. $associationMapping['targetEntity']
  1082. );
  1083. $methods[] = $this->generateEntityStubMethod(
  1084. $metadata,
  1085. 'get',
  1086. $associationMapping['fieldName'],
  1087. Collection::class
  1088. );
  1089. }
  1090. }
  1091. return implode("\n\n", array_filter($methods));
  1092. }
  1093. /**
  1094. * @psalm-param array<string, mixed> $associationMapping
  1095. *
  1096. * @return bool
  1097. */
  1098. protected function isAssociationIsNullable(array $associationMapping)
  1099. {
  1100. if (isset($associationMapping['id']) && $associationMapping['id']) {
  1101. return false;
  1102. }
  1103. if (isset($associationMapping['joinColumns'])) {
  1104. $joinColumns = $associationMapping['joinColumns'];
  1105. } else {
  1106. //@todo there is no way to retrieve targetEntity metadata
  1107. $joinColumns = [];
  1108. }
  1109. foreach ($joinColumns as $joinColumn) {
  1110. if (isset($joinColumn['nullable']) && ! $joinColumn['nullable']) {
  1111. return false;
  1112. }
  1113. }
  1114. return true;
  1115. }
  1116. /**
  1117. * @return string
  1118. */
  1119. protected function generateEntityLifecycleCallbackMethods(ClassMetadataInfo $metadata)
  1120. {
  1121. if (empty($metadata->lifecycleCallbacks)) {
  1122. return '';
  1123. }
  1124. $methods = [];
  1125. foreach ($metadata->lifecycleCallbacks as $name => $callbacks) {
  1126. foreach ($callbacks as $callback) {
  1127. $methods[] = $this->generateLifecycleCallbackMethod($name, $callback, $metadata);
  1128. }
  1129. }
  1130. return implode("\n\n", array_filter($methods));
  1131. }
  1132. /**
  1133. * @return string
  1134. */
  1135. protected function generateEntityAssociationMappingProperties(ClassMetadataInfo $metadata)
  1136. {
  1137. $lines = [];
  1138. foreach ($metadata->associationMappings as $associationMapping) {
  1139. if ($this->hasProperty($associationMapping['fieldName'], $metadata)) {
  1140. continue;
  1141. }
  1142. $lines[] = $this->generateAssociationMappingPropertyDocBlock($associationMapping, $metadata);
  1143. $lines[] = $this->spaces . $this->fieldVisibility . ' $' . $associationMapping['fieldName']
  1144. . ($associationMapping['type'] === 'manyToMany' ? ' = array()' : null) . ";\n";
  1145. }
  1146. return implode("\n", $lines);
  1147. }
  1148. /**
  1149. * @return string
  1150. */
  1151. protected function generateEntityFieldMappingProperties(ClassMetadataInfo $metadata)
  1152. {
  1153. $lines = [];
  1154. foreach ($metadata->fieldMappings as $fieldMapping) {
  1155. if (
  1156. isset($fieldMapping['declaredField'], $metadata->embeddedClasses[$fieldMapping['declaredField']]) ||
  1157. $this->hasProperty($fieldMapping['fieldName'], $metadata) ||
  1158. $metadata->isInheritedField($fieldMapping['fieldName'])
  1159. ) {
  1160. continue;
  1161. }
  1162. $defaultValue = '';
  1163. if (isset($fieldMapping['options']['default'])) {
  1164. if ($fieldMapping['type'] === 'boolean' && $fieldMapping['options']['default'] === '1') {
  1165. $defaultValue = ' = true';
  1166. } elseif (($fieldMapping['type'] === 'integer' || $fieldMapping['type'] === 'float') && ! empty($fieldMapping['options']['default'])) {
  1167. $defaultValue = ' = ' . (string) $fieldMapping['options']['default'];
  1168. } else {
  1169. $defaultValue = ' = ' . var_export($fieldMapping['options']['default'], true);
  1170. }
  1171. }
  1172. $lines[] = $this->generateFieldMappingPropertyDocBlock($fieldMapping, $metadata);
  1173. $lines[] = $this->spaces . $this->fieldVisibility . ' $' . $fieldMapping['fieldName'] . $defaultValue . ";\n";
  1174. }
  1175. return implode("\n", $lines);
  1176. }
  1177. /**
  1178. * @return string
  1179. */
  1180. protected function generateEntityEmbeddedProperties(ClassMetadataInfo $metadata)
  1181. {
  1182. $lines = [];
  1183. foreach ($metadata->embeddedClasses as $fieldName => $embeddedClass) {
  1184. if (isset($embeddedClass['declaredField']) || $this->hasProperty($fieldName, $metadata)) {
  1185. continue;
  1186. }
  1187. $lines[] = $this->generateEmbeddedPropertyDocBlock($embeddedClass);
  1188. $lines[] = $this->spaces . $this->fieldVisibility . ' $' . $fieldName . ";\n";
  1189. }
  1190. return implode("\n", $lines);
  1191. }
  1192. /**
  1193. * @param string $type
  1194. * @param string $fieldName
  1195. * @param string|null $typeHint
  1196. * @param string|null $defaultValue
  1197. *
  1198. * @return string
  1199. */
  1200. protected function generateEntityStubMethod(ClassMetadataInfo $metadata, $type, $fieldName, $typeHint = null, $defaultValue = null)
  1201. {
  1202. $methodName = $type . $this->inflector->classify($fieldName);
  1203. $variableName = $this->inflector->camelize($fieldName);
  1204. if (in_array($type, ['add', 'remove'], true)) {
  1205. $methodName = $this->inflector->singularize($methodName);
  1206. $variableName = $this->inflector->singularize($variableName);
  1207. }
  1208. if ($this->hasMethod($methodName, $metadata)) {
  1209. return '';
  1210. }
  1211. $this->staticReflection[$metadata->name]['methods'][] = strtolower($methodName);
  1212. $var = sprintf('%sMethodTemplate', $type);
  1213. $template = static::$$var;
  1214. $methodTypeHint = '';
  1215. $types = Type::getTypesMap();
  1216. $variableType = $typeHint ? $this->getType($typeHint) : null;
  1217. if ($typeHint && ! isset($types[$typeHint])) {
  1218. $variableType = '\\' . ltrim($variableType, '\\');
  1219. $methodTypeHint = '\\' . $typeHint . ' ';
  1220. }
  1221. $replacements = [
  1222. '<description>' => ucfirst($type) . ' ' . $variableName . '.',
  1223. '<methodTypeHint>' => $methodTypeHint,
  1224. '<variableType>' => $variableType . ($defaultValue !== null ? '|' . $defaultValue : ''),
  1225. '<variableName>' => $variableName,
  1226. '<methodName>' => $methodName,
  1227. '<fieldName>' => $fieldName,
  1228. '<variableDefault>' => $defaultValue !== null ? ' = ' . $defaultValue : '',
  1229. '<entity>' => $this->getClassName($metadata),
  1230. ];
  1231. $method = str_replace(
  1232. array_keys($replacements),
  1233. array_values($replacements),
  1234. $template
  1235. );
  1236. return $this->prefixCodeWithSpaces($method);
  1237. }
  1238. /**
  1239. * @param string $name
  1240. * @param string $methodName
  1241. *
  1242. * @return string
  1243. */
  1244. protected function generateLifecycleCallbackMethod($name, $methodName, ClassMetadataInfo $metadata)
  1245. {
  1246. if ($this->hasMethod($methodName, $metadata)) {
  1247. return '';
  1248. }
  1249. $this->staticReflection[$metadata->name]['methods'][] = $methodName;
  1250. $replacements = [
  1251. '<name>' => $this->annotationsPrefix . ucfirst($name),
  1252. '<methodName>' => $methodName,
  1253. ];
  1254. $method = str_replace(
  1255. array_keys($replacements),
  1256. array_values($replacements),
  1257. static::$lifecycleCallbackMethodTemplate
  1258. );
  1259. return $this->prefixCodeWithSpaces($method);
  1260. }
  1261. /**
  1262. * @psalm-param array<string, mixed> $joinColumn
  1263. *
  1264. * @return string
  1265. */
  1266. protected function generateJoinColumnAnnotation(array $joinColumn)
  1267. {
  1268. $joinColumnAnnot = [];
  1269. if (isset($joinColumn['name'])) {
  1270. $joinColumnAnnot[] = 'name="' . $joinColumn['name'] . '"';
  1271. }
  1272. if (isset($joinColumn['referencedColumnName'])) {
  1273. $joinColumnAnnot[] = 'referencedColumnName="' . $joinColumn['referencedColumnName'] . '"';
  1274. }
  1275. if (isset($joinColumn['unique']) && $joinColumn['unique']) {
  1276. $joinColumnAnnot[] = 'unique=true';
  1277. }
  1278. if (isset($joinColumn['nullable'])) {
  1279. $joinColumnAnnot[] = 'nullable=' . ($joinColumn['nullable'] ? 'true' : 'false');
  1280. }
  1281. if (isset($joinColumn['onDelete'])) {
  1282. $joinColumnAnnot[] = 'onDelete="' . $joinColumn['onDelete'] . '"';
  1283. }
  1284. if (isset($joinColumn['columnDefinition'])) {
  1285. $joinColumnAnnot[] = 'columnDefinition="' . $joinColumn['columnDefinition'] . '"';
  1286. }
  1287. return '@' . $this->annotationsPrefix . 'JoinColumn(' . implode(', ', $joinColumnAnnot) . ')';
  1288. }
  1289. /**
  1290. * @param mixed[] $associationMapping
  1291. *
  1292. * @return string
  1293. */
  1294. protected function generateAssociationMappingPropertyDocBlock(array $associationMapping, ClassMetadataInfo $metadata)
  1295. {
  1296. $lines = [];
  1297. $lines[] = $this->spaces . '/**';
  1298. if ($associationMapping['type'] & ClassMetadataInfo::TO_MANY) {
  1299. $lines[] = $this->spaces . ' * @var \Doctrine\Common\Collections\Collection';
  1300. } else {
  1301. $lines[] = $this->spaces . ' * @var \\' . ltrim($associationMapping['targetEntity'], '\\');
  1302. }
  1303. if ($this->generateAnnotations) {
  1304. $lines[] = $this->spaces . ' *';
  1305. if (isset($associationMapping['id']) && $associationMapping['id']) {
  1306. $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'Id';
  1307. $generatorType = $this->getIdGeneratorTypeString($metadata->generatorType);
  1308. if ($generatorType) {
  1309. $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'GeneratedValue(strategy="' . $generatorType . '")';
  1310. }
  1311. }
  1312. $type = null;
  1313. switch ($associationMapping['type']) {
  1314. case ClassMetadataInfo::ONE_TO_ONE:
  1315. $type = 'OneToOne';
  1316. break;
  1317. case ClassMetadataInfo::MANY_TO_ONE:
  1318. $type = 'ManyToOne';
  1319. break;
  1320. case ClassMetadataInfo::ONE_TO_MANY:
  1321. $type = 'OneToMany';
  1322. break;
  1323. case ClassMetadataInfo::MANY_TO_MANY:
  1324. $type = 'ManyToMany';
  1325. break;
  1326. }
  1327. $typeOptions = [];
  1328. if (isset($associationMapping['targetEntity'])) {
  1329. $typeOptions[] = 'targetEntity="' . $associationMapping['targetEntity'] . '"';
  1330. }
  1331. if (isset($associationMapping['inversedBy'])) {
  1332. $typeOptions[] = 'inversedBy="' . $associationMapping['inversedBy'] . '"';
  1333. }
  1334. if (isset($associationMapping['mappedBy'])) {
  1335. $typeOptions[] = 'mappedBy="' . $associationMapping['mappedBy'] . '"';
  1336. }
  1337. if ($associationMapping['cascade']) {
  1338. $cascades = [];
  1339. if ($associationMapping['isCascadePersist']) {
  1340. $cascades[] = '"persist"';
  1341. }
  1342. if ($associationMapping['isCascadeRemove']) {
  1343. $cascades[] = '"remove"';
  1344. }
  1345. if ($associationMapping['isCascadeDetach']) {
  1346. $cascades[] = '"detach"';
  1347. }
  1348. if ($associationMapping['isCascadeMerge']) {
  1349. $cascades[] = '"merge"';
  1350. }
  1351. if ($associationMapping['isCascadeRefresh']) {
  1352. $cascades[] = '"refresh"';
  1353. }
  1354. if (count($cascades) === 5) {
  1355. $cascades = ['"all"'];
  1356. }
  1357. $typeOptions[] = 'cascade={' . implode(',', $cascades) . '}';
  1358. }
  1359. if (isset($associationMapping['orphanRemoval']) && $associationMapping['orphanRemoval']) {
  1360. $typeOptions[] = 'orphanRemoval=true';
  1361. }
  1362. if (isset($associationMapping['fetch']) && $associationMapping['fetch'] !== ClassMetadataInfo::FETCH_LAZY) {
  1363. $fetchMap = [
  1364. ClassMetadataInfo::FETCH_EXTRA_LAZY => 'EXTRA_LAZY',
  1365. ClassMetadataInfo::FETCH_EAGER => 'EAGER',
  1366. ];
  1367. $typeOptions[] = 'fetch="' . $fetchMap[$associationMapping['fetch']] . '"';
  1368. }
  1369. $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . '' . $type . '(' . implode(', ', $typeOptions) . ')';
  1370. if (isset($associationMapping['joinColumns']) && $associationMapping['joinColumns']) {
  1371. $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'JoinColumns({';
  1372. $joinColumnsLines = [];
  1373. foreach ($associationMapping['joinColumns'] as $joinColumn) {
  1374. $joinColumnAnnot = $this->generateJoinColumnAnnotation($joinColumn);
  1375. if ($joinColumnAnnot) {
  1376. $joinColumnsLines[] = $this->spaces . ' * ' . $joinColumnAnnot;
  1377. }
  1378. }
  1379. $lines[] = implode(",\n", $joinColumnsLines);
  1380. $lines[] = $this->spaces . ' * })';
  1381. }
  1382. if (isset($associationMapping['joinTable']) && $associationMapping[

Large files files are truncated, but you can click here to view the full file