vendor/doctrine/orm/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php line 363

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace Doctrine\ORM\Mapping\Driver;
  4. use Doctrine\Common\Annotations\AnnotationReader;
  5. use Doctrine\Common\Annotations\Reader;
  6. use Doctrine\Deprecations\Deprecation;
  7. use Doctrine\ORM\Events;
  8. use Doctrine\ORM\Mapping;
  9. use Doctrine\ORM\Mapping\Builder\EntityListenerBuilder;
  10. use Doctrine\ORM\Mapping\ClassMetadata;
  11. use Doctrine\ORM\Mapping\MappingException;
  12. use Doctrine\Persistence\Mapping\ClassMetadata as PersistenceClassMetadata;
  13. use Doctrine\Persistence\Mapping\Driver\ColocatedMappingDriver;
  14. use ReflectionClass;
  15. use ReflectionMethod;
  16. use ReflectionProperty;
  17. use UnexpectedValueException;
  18. use function assert;
  19. use function class_exists;
  20. use function constant;
  21. use function count;
  22. use function defined;
  23. use function get_class;
  24. use function is_array;
  25. use function is_numeric;
  26. /**
  27. * The AnnotationDriver reads the mapping metadata from docblock annotations.
  28. */
  29. class AnnotationDriver extends CompatibilityAnnotationDriver
  30. {
  31. use ColocatedMappingDriver;
  32. /**
  33. * The annotation reader.
  34. *
  35. * @internal this property will be private in 3.0
  36. *
  37. * @var Reader
  38. */
  39. protected $reader;
  40. /**
  41. * @var int[]
  42. * @psalm-var array<class-string, int>
  43. */
  44. protected $entityAnnotationClasses = [
  45. Mapping\Entity::class => 1,
  46. Mapping\MappedSuperclass::class => 2,
  47. ];
  48. /**
  49. * Initializes a new AnnotationDriver that uses the given AnnotationReader for reading
  50. * docblock annotations.
  51. *
  52. * @param Reader $reader The AnnotationReader to use
  53. * @param string|string[]|null $paths One or multiple paths where mapping classes can be found.
  54. */
  55. public function __construct($reader, $paths = null)
  56. {
  57. Deprecation::trigger(
  58. 'doctrine/orm',
  59. 'https://github.com/doctrine/orm/issues/10098',
  60. 'The annotation mapping driver is deprecated and will be removed in Doctrine ORM 3.0, please migrate to the attribute or XML driver.'
  61. );
  62. $this->reader = $reader;
  63. $this->addPaths((array) $paths);
  64. }
  65. /**
  66. * {@inheritDoc}
  67. *
  68. * @psalm-param class-string<T> $className
  69. * @psalm-param ClassMetadata<T> $metadata
  70. *
  71. * @template T of object
  72. */
  73. public function loadMetadataForClass($className, PersistenceClassMetadata $metadata)
  74. {
  75. $class = $metadata->getReflectionClass()
  76. // this happens when running annotation driver in combination with
  77. // static reflection services. This is not the nicest fix
  78. ?? new ReflectionClass($metadata->name);
  79. $classAnnotations = $this->reader->getClassAnnotations($class);
  80. foreach ($classAnnotations as $key => $annot) {
  81. if (! is_numeric($key)) {
  82. continue;
  83. }
  84. $classAnnotations[get_class($annot)] = $annot;
  85. }
  86. // Evaluate Entity annotation
  87. if (isset($classAnnotations[Mapping\Entity::class])) {
  88. $entityAnnot = $classAnnotations[Mapping\Entity::class];
  89. assert($entityAnnot instanceof Mapping\Entity);
  90. if ($entityAnnot->repositoryClass !== null) {
  91. $metadata->setCustomRepositoryClass($entityAnnot->repositoryClass);
  92. }
  93. if ($entityAnnot->readOnly) {
  94. $metadata->markReadOnly();
  95. }
  96. } elseif (isset($classAnnotations[Mapping\MappedSuperclass::class])) {
  97. $mappedSuperclassAnnot = $classAnnotations[Mapping\MappedSuperclass::class];
  98. assert($mappedSuperclassAnnot instanceof Mapping\MappedSuperclass);
  99. $metadata->setCustomRepositoryClass($mappedSuperclassAnnot->repositoryClass);
  100. $metadata->isMappedSuperclass = true;
  101. } elseif (isset($classAnnotations[Mapping\Embeddable::class])) {
  102. $metadata->isEmbeddedClass = true;
  103. } else {
  104. throw MappingException::classIsNotAValidEntityOrMappedSuperClass($className);
  105. }
  106. // Evaluate Table annotation
  107. if (isset($classAnnotations[Mapping\Table::class])) {
  108. $tableAnnot = $classAnnotations[Mapping\Table::class];
  109. assert($tableAnnot instanceof Mapping\Table);
  110. $primaryTable = [
  111. 'name' => $tableAnnot->name,
  112. 'schema' => $tableAnnot->schema,
  113. ];
  114. foreach ($tableAnnot->indexes ?? [] as $indexAnnot) {
  115. $index = [];
  116. if (! empty($indexAnnot->columns)) {
  117. $index['columns'] = $indexAnnot->columns;
  118. }
  119. if (! empty($indexAnnot->fields)) {
  120. $index['fields'] = $indexAnnot->fields;
  121. }
  122. if (
  123. isset($index['columns'], $index['fields'])
  124. || (
  125. ! isset($index['columns'])
  126. && ! isset($index['fields'])
  127. )
  128. ) {
  129. throw MappingException::invalidIndexConfiguration(
  130. $className,
  131. (string) ($indexAnnot->name ?? count($primaryTable['indexes']))
  132. );
  133. }
  134. if (! empty($indexAnnot->flags)) {
  135. $index['flags'] = $indexAnnot->flags;
  136. }
  137. if (! empty($indexAnnot->options)) {
  138. $index['options'] = $indexAnnot->options;
  139. }
  140. if (! empty($indexAnnot->name)) {
  141. $primaryTable['indexes'][$indexAnnot->name] = $index;
  142. } else {
  143. $primaryTable['indexes'][] = $index;
  144. }
  145. }
  146. foreach ($tableAnnot->uniqueConstraints ?? [] as $uniqueConstraintAnnot) {
  147. $uniqueConstraint = [];
  148. if (! empty($uniqueConstraintAnnot->columns)) {
  149. $uniqueConstraint['columns'] = $uniqueConstraintAnnot->columns;
  150. }
  151. if (! empty($uniqueConstraintAnnot->fields)) {
  152. $uniqueConstraint['fields'] = $uniqueConstraintAnnot->fields;
  153. }
  154. if (
  155. isset($uniqueConstraint['columns'], $uniqueConstraint['fields'])
  156. || (
  157. ! isset($uniqueConstraint['columns'])
  158. && ! isset($uniqueConstraint['fields'])
  159. )
  160. ) {
  161. throw MappingException::invalidUniqueConstraintConfiguration(
  162. $className,
  163. (string) ($uniqueConstraintAnnot->name ?? count($primaryTable['uniqueConstraints']))
  164. );
  165. }
  166. if (! empty($uniqueConstraintAnnot->options)) {
  167. $uniqueConstraint['options'] = $uniqueConstraintAnnot->options;
  168. }
  169. if (! empty($uniqueConstraintAnnot->name)) {
  170. $primaryTable['uniqueConstraints'][$uniqueConstraintAnnot->name] = $uniqueConstraint;
  171. } else {
  172. $primaryTable['uniqueConstraints'][] = $uniqueConstraint;
  173. }
  174. }
  175. if ($tableAnnot->options) {
  176. $primaryTable['options'] = $tableAnnot->options;
  177. }
  178. $metadata->setPrimaryTable($primaryTable);
  179. }
  180. // Evaluate @Cache annotation
  181. if (isset($classAnnotations[Mapping\Cache::class])) {
  182. $cacheAnnot = $classAnnotations[Mapping\Cache::class];
  183. $cacheMap = [
  184. 'region' => $cacheAnnot->region,
  185. 'usage' => (int) constant('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' . $cacheAnnot->usage),
  186. ];
  187. $metadata->enableCache($cacheMap);
  188. }
  189. // Evaluate NamedNativeQueries annotation
  190. if (isset($classAnnotations[Mapping\NamedNativeQueries::class])) {
  191. $namedNativeQueriesAnnot = $classAnnotations[Mapping\NamedNativeQueries::class];
  192. foreach ($namedNativeQueriesAnnot->value as $namedNativeQuery) {
  193. $metadata->addNamedNativeQuery(
  194. [
  195. 'name' => $namedNativeQuery->name,
  196. 'query' => $namedNativeQuery->query,
  197. 'resultClass' => $namedNativeQuery->resultClass,
  198. 'resultSetMapping' => $namedNativeQuery->resultSetMapping,
  199. ]
  200. );
  201. }
  202. }
  203. // Evaluate SqlResultSetMappings annotation
  204. if (isset($classAnnotations[Mapping\SqlResultSetMappings::class])) {
  205. $sqlResultSetMappingsAnnot = $classAnnotations[Mapping\SqlResultSetMappings::class];
  206. foreach ($sqlResultSetMappingsAnnot->value as $resultSetMapping) {
  207. $entities = [];
  208. $columns = [];
  209. foreach ($resultSetMapping->entities as $entityResultAnnot) {
  210. $entityResult = [
  211. 'fields' => [],
  212. 'entityClass' => $entityResultAnnot->entityClass,
  213. 'discriminatorColumn' => $entityResultAnnot->discriminatorColumn,
  214. ];
  215. foreach ($entityResultAnnot->fields as $fieldResultAnnot) {
  216. $entityResult['fields'][] = [
  217. 'name' => $fieldResultAnnot->name,
  218. 'column' => $fieldResultAnnot->column,
  219. ];
  220. }
  221. $entities[] = $entityResult;
  222. }
  223. foreach ($resultSetMapping->columns as $columnResultAnnot) {
  224. $columns[] = [
  225. 'name' => $columnResultAnnot->name,
  226. ];
  227. }
  228. $metadata->addSqlResultSetMapping(
  229. [
  230. 'name' => $resultSetMapping->name,
  231. 'entities' => $entities,
  232. 'columns' => $columns,
  233. ]
  234. );
  235. }
  236. }
  237. // Evaluate NamedQueries annotation
  238. if (isset($classAnnotations[Mapping\NamedQueries::class])) {
  239. $namedQueriesAnnot = $classAnnotations[Mapping\NamedQueries::class];
  240. if (! is_array($namedQueriesAnnot->value)) {
  241. throw new UnexpectedValueException('@NamedQueries should contain an array of @NamedQuery annotations.');
  242. }
  243. foreach ($namedQueriesAnnot->value as $namedQuery) {
  244. if (! ($namedQuery instanceof Mapping\NamedQuery)) {
  245. throw new UnexpectedValueException('@NamedQueries should contain an array of @NamedQuery annotations.');
  246. }
  247. $metadata->addNamedQuery(
  248. [
  249. 'name' => $namedQuery->name,
  250. 'query' => $namedQuery->query,
  251. ]
  252. );
  253. }
  254. }
  255. // Evaluate InheritanceType annotation
  256. if (isset($classAnnotations[Mapping\InheritanceType::class])) {
  257. $inheritanceTypeAnnot = $classAnnotations[Mapping\InheritanceType::class];
  258. assert($inheritanceTypeAnnot instanceof Mapping\InheritanceType);
  259. $metadata->setInheritanceType(
  260. constant('Doctrine\ORM\Mapping\ClassMetadata::INHERITANCE_TYPE_' . $inheritanceTypeAnnot->value)
  261. );
  262. if ($metadata->inheritanceType !== ClassMetadata::INHERITANCE_TYPE_NONE) {
  263. // Evaluate DiscriminatorColumn annotation
  264. if (isset($classAnnotations[Mapping\DiscriminatorColumn::class])) {
  265. $discrColumnAnnot = $classAnnotations[Mapping\DiscriminatorColumn::class];
  266. assert($discrColumnAnnot instanceof Mapping\DiscriminatorColumn);
  267. $metadata->setDiscriminatorColumn(
  268. [
  269. 'name' => $discrColumnAnnot->name,
  270. 'type' => $discrColumnAnnot->type ?: 'string',
  271. 'length' => $discrColumnAnnot->length ?? 255,
  272. 'columnDefinition' => $discrColumnAnnot->columnDefinition,
  273. 'enumType' => $discrColumnAnnot->enumType,
  274. ]
  275. );
  276. } else {
  277. $metadata->setDiscriminatorColumn(['name' => 'dtype', 'type' => 'string', 'length' => 255]);
  278. }
  279. // Evaluate DiscriminatorMap annotation
  280. if (isset($classAnnotations[Mapping\DiscriminatorMap::class])) {
  281. $discrMapAnnot = $classAnnotations[Mapping\DiscriminatorMap::class];
  282. assert($discrMapAnnot instanceof Mapping\DiscriminatorMap);
  283. $metadata->setDiscriminatorMap($discrMapAnnot->value);
  284. }
  285. }
  286. }
  287. // Evaluate DoctrineChangeTrackingPolicy annotation
  288. if (isset($classAnnotations[Mapping\ChangeTrackingPolicy::class])) {
  289. $changeTrackingAnnot = $classAnnotations[Mapping\ChangeTrackingPolicy::class];
  290. assert($changeTrackingAnnot instanceof Mapping\ChangeTrackingPolicy);
  291. $metadata->setChangeTrackingPolicy(constant('Doctrine\ORM\Mapping\ClassMetadata::CHANGETRACKING_' . $changeTrackingAnnot->value));
  292. }
  293. // Evaluate annotations on properties/fields
  294. foreach ($class->getProperties() as $property) {
  295. if (
  296. $metadata->isMappedSuperclass && ! $property->isPrivate()
  297. ||
  298. $metadata->isInheritedField($property->name)
  299. ||
  300. $metadata->isInheritedAssociation($property->name)
  301. ||
  302. $metadata->isInheritedEmbeddedClass($property->name)
  303. ) {
  304. continue;
  305. }
  306. $mapping = [];
  307. $mapping['fieldName'] = $property->getName();
  308. // Evaluate @Cache annotation
  309. $cacheAnnot = $this->reader->getPropertyAnnotation($property, Mapping\Cache::class);
  310. if ($cacheAnnot !== null) {
  311. $mapping['cache'] = $metadata->getAssociationCacheDefaults(
  312. $mapping['fieldName'],
  313. [
  314. 'usage' => (int) constant('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' . $cacheAnnot->usage),
  315. 'region' => $cacheAnnot->region,
  316. ]
  317. );
  318. }
  319. // Check for JoinColumn/JoinColumns annotations
  320. $joinColumns = [];
  321. $joinColumnAnnot = $this->reader->getPropertyAnnotation($property, Mapping\JoinColumn::class);
  322. if ($joinColumnAnnot) {
  323. $joinColumns[] = $this->joinColumnToArray($joinColumnAnnot);
  324. } else {
  325. $joinColumnsAnnot = $this->reader->getPropertyAnnotation($property, Mapping\JoinColumns::class);
  326. if ($joinColumnsAnnot) {
  327. foreach ($joinColumnsAnnot->value as $joinColumn) {
  328. $joinColumns[] = $this->joinColumnToArray($joinColumn);
  329. }
  330. }
  331. }
  332. // Field can only be annotated with one of:
  333. // @Column, @OneToOne, @OneToMany, @ManyToOne, @ManyToMany
  334. $columnAnnot = $this->reader->getPropertyAnnotation($property, Mapping\Column::class);
  335. if ($columnAnnot) {
  336. $mapping = $this->columnToArray($property->getName(), $columnAnnot);
  337. $idAnnot = $this->reader->getPropertyAnnotation($property, Mapping\Id::class);
  338. if ($idAnnot) {
  339. $mapping['id'] = true;
  340. }
  341. $generatedValueAnnot = $this->reader->getPropertyAnnotation($property, Mapping\GeneratedValue::class);
  342. if ($generatedValueAnnot) {
  343. $metadata->setIdGeneratorType(constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_' . $generatedValueAnnot->strategy));
  344. }
  345. if ($this->reader->getPropertyAnnotation($property, Mapping\Version::class)) {
  346. $metadata->setVersionMapping($mapping);
  347. }
  348. $metadata->mapField($mapping);
  349. // Check for SequenceGenerator/TableGenerator definition
  350. $seqGeneratorAnnot = $this->reader->getPropertyAnnotation($property, Mapping\SequenceGenerator::class);
  351. if ($seqGeneratorAnnot) {
  352. $metadata->setSequenceGeneratorDefinition(
  353. [
  354. 'sequenceName' => $seqGeneratorAnnot->sequenceName,
  355. 'allocationSize' => $seqGeneratorAnnot->allocationSize,
  356. 'initialValue' => $seqGeneratorAnnot->initialValue,
  357. ]
  358. );
  359. } else {
  360. $customGeneratorAnnot = $this->reader->getPropertyAnnotation($property, Mapping\CustomIdGenerator::class);
  361. if ($customGeneratorAnnot) {
  362. $metadata->setCustomGeneratorDefinition(
  363. [
  364. 'class' => $customGeneratorAnnot->class,
  365. ]
  366. );
  367. }
  368. }
  369. } else {
  370. $this->loadRelationShipMapping(
  371. $property,
  372. $mapping,
  373. $metadata,
  374. $joinColumns,
  375. $className
  376. );
  377. }
  378. }
  379. // Evaluate AssociationOverrides annotation
  380. if (isset($classAnnotations[Mapping\AssociationOverrides::class])) {
  381. $associationOverridesAnnot = $classAnnotations[Mapping\AssociationOverrides::class];
  382. assert($associationOverridesAnnot instanceof Mapping\AssociationOverrides);
  383. foreach ($associationOverridesAnnot->overrides as $associationOverride) {
  384. $override = [];
  385. $fieldName = $associationOverride->name;
  386. // Check for JoinColumn/JoinColumns annotations
  387. if ($associationOverride->joinColumns) {
  388. $joinColumns = [];
  389. foreach ($associationOverride->joinColumns as $joinColumn) {
  390. $joinColumns[] = $this->joinColumnToArray($joinColumn);
  391. }
  392. $override['joinColumns'] = $joinColumns;
  393. }
  394. // Check for JoinTable annotations
  395. if ($associationOverride->joinTable) {
  396. $joinTableAnnot = $associationOverride->joinTable;
  397. $joinTable = [
  398. 'name' => $joinTableAnnot->name,
  399. 'schema' => $joinTableAnnot->schema,
  400. ];
  401. foreach ($joinTableAnnot->joinColumns as $joinColumn) {
  402. $joinTable['joinColumns'][] = $this->joinColumnToArray($joinColumn);
  403. }
  404. foreach ($joinTableAnnot->inverseJoinColumns as $joinColumn) {
  405. $joinTable['inverseJoinColumns'][] = $this->joinColumnToArray($joinColumn);
  406. }
  407. $override['joinTable'] = $joinTable;
  408. }
  409. // Check for inversedBy
  410. if ($associationOverride->inversedBy) {
  411. $override['inversedBy'] = $associationOverride->inversedBy;
  412. }
  413. // Check for `fetch`
  414. if ($associationOverride->fetch) {
  415. $override['fetch'] = constant(Mapping\ClassMetadata::class . '::FETCH_' . $associationOverride->fetch);
  416. }
  417. $metadata->setAssociationOverride($fieldName, $override);
  418. }
  419. }
  420. // Evaluate AttributeOverrides annotation
  421. if (isset($classAnnotations[Mapping\AttributeOverrides::class])) {
  422. $attributeOverridesAnnot = $classAnnotations[Mapping\AttributeOverrides::class];
  423. assert($attributeOverridesAnnot instanceof Mapping\AttributeOverrides);
  424. foreach ($attributeOverridesAnnot->overrides as $attributeOverrideAnnot) {
  425. $attributeOverride = $this->columnToArray($attributeOverrideAnnot->name, $attributeOverrideAnnot->column);
  426. $metadata->setAttributeOverride($attributeOverrideAnnot->name, $attributeOverride);
  427. }
  428. }
  429. // Evaluate EntityListeners annotation
  430. if (isset($classAnnotations[Mapping\EntityListeners::class])) {
  431. $entityListenersAnnot = $classAnnotations[Mapping\EntityListeners::class];
  432. assert($entityListenersAnnot instanceof Mapping\EntityListeners);
  433. foreach ($entityListenersAnnot->value as $item) {
  434. $listenerClassName = $metadata->fullyQualifiedClassName($item);
  435. if (! class_exists($listenerClassName)) {
  436. throw MappingException::entityListenerClassNotFound($listenerClassName, $className);
  437. }
  438. $hasMapping = false;
  439. $listenerClass = new ReflectionClass($listenerClassName);
  440. foreach ($listenerClass->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
  441. // find method callbacks.
  442. $callbacks = $this->getMethodCallbacks($method);
  443. $hasMapping = $hasMapping ?: ! empty($callbacks);
  444. foreach ($callbacks as $value) {
  445. $metadata->addEntityListener($value[1], $listenerClassName, $value[0]);
  446. }
  447. }
  448. // Evaluate the listener using naming convention.
  449. if (! $hasMapping) {
  450. EntityListenerBuilder::bindEntityListener($metadata, $listenerClassName);
  451. }
  452. }
  453. }
  454. // Evaluate @HasLifecycleCallbacks annotation
  455. if (isset($classAnnotations[Mapping\HasLifecycleCallbacks::class])) {
  456. foreach ($class->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
  457. foreach ($this->getMethodCallbacks($method) as $value) {
  458. $metadata->addLifecycleCallback($value[0], $value[1]);
  459. }
  460. }
  461. }
  462. }
  463. /**
  464. * @param mixed[] $joinColumns
  465. * @param class-string $className
  466. * @param array<string, mixed> $mapping
  467. */
  468. private function loadRelationShipMapping(
  469. ReflectionProperty $property,
  470. array &$mapping,
  471. PersistenceClassMetadata $metadata,
  472. array $joinColumns,
  473. string $className
  474. ): void {
  475. $oneToOneAnnot = $this->reader->getPropertyAnnotation($property, Mapping\OneToOne::class);
  476. if ($oneToOneAnnot) {
  477. $idAnnot = $this->reader->getPropertyAnnotation($property, Mapping\Id::class);
  478. if ($idAnnot) {
  479. $mapping['id'] = true;
  480. }
  481. $mapping['targetEntity'] = $oneToOneAnnot->targetEntity;
  482. $mapping['joinColumns'] = $joinColumns;
  483. $mapping['mappedBy'] = $oneToOneAnnot->mappedBy;
  484. $mapping['inversedBy'] = $oneToOneAnnot->inversedBy;
  485. $mapping['cascade'] = $oneToOneAnnot->cascade;
  486. $mapping['orphanRemoval'] = $oneToOneAnnot->orphanRemoval;
  487. $mapping['fetch'] = $this->getFetchMode($className, $oneToOneAnnot->fetch);
  488. $metadata->mapOneToOne($mapping);
  489. return;
  490. }
  491. $oneToManyAnnot = $this->reader->getPropertyAnnotation($property, Mapping\OneToMany::class);
  492. if ($oneToManyAnnot) {
  493. $mapping['mappedBy'] = $oneToManyAnnot->mappedBy;
  494. $mapping['targetEntity'] = $oneToManyAnnot->targetEntity;
  495. $mapping['cascade'] = $oneToManyAnnot->cascade;
  496. $mapping['indexBy'] = $oneToManyAnnot->indexBy;
  497. $mapping['orphanRemoval'] = $oneToManyAnnot->orphanRemoval;
  498. $mapping['fetch'] = $this->getFetchMode($className, $oneToManyAnnot->fetch);
  499. $orderByAnnot = $this->reader->getPropertyAnnotation($property, Mapping\OrderBy::class);
  500. if ($orderByAnnot) {
  501. $mapping['orderBy'] = $orderByAnnot->value;
  502. }
  503. $metadata->mapOneToMany($mapping);
  504. }
  505. $manyToOneAnnot = $this->reader->getPropertyAnnotation($property, Mapping\ManyToOne::class);
  506. if ($manyToOneAnnot) {
  507. $idAnnot = $this->reader->getPropertyAnnotation($property, Mapping\Id::class);
  508. if ($idAnnot) {
  509. $mapping['id'] = true;
  510. }
  511. $mapping['joinColumns'] = $joinColumns;
  512. $mapping['cascade'] = $manyToOneAnnot->cascade;
  513. $mapping['inversedBy'] = $manyToOneAnnot->inversedBy;
  514. $mapping['targetEntity'] = $manyToOneAnnot->targetEntity;
  515. $mapping['fetch'] = $this->getFetchMode($className, $manyToOneAnnot->fetch);
  516. $metadata->mapManyToOne($mapping);
  517. }
  518. $manyToManyAnnot = $this->reader->getPropertyAnnotation($property, Mapping\ManyToMany::class);
  519. if ($manyToManyAnnot) {
  520. $joinTable = [];
  521. $joinTableAnnot = $this->reader->getPropertyAnnotation($property, Mapping\JoinTable::class);
  522. if ($joinTableAnnot) {
  523. $joinTable = [
  524. 'name' => $joinTableAnnot->name,
  525. 'schema' => $joinTableAnnot->schema,
  526. ];
  527. if ($joinTableAnnot->options) {
  528. $joinTable['options'] = $joinTableAnnot->options;
  529. }
  530. foreach ($joinTableAnnot->joinColumns as $joinColumn) {
  531. $joinTable['joinColumns'][] = $this->joinColumnToArray($joinColumn);
  532. }
  533. foreach ($joinTableAnnot->inverseJoinColumns as $joinColumn) {
  534. $joinTable['inverseJoinColumns'][] = $this->joinColumnToArray($joinColumn);
  535. }
  536. }
  537. $mapping['joinTable'] = $joinTable;
  538. $mapping['targetEntity'] = $manyToManyAnnot->targetEntity;
  539. $mapping['mappedBy'] = $manyToManyAnnot->mappedBy;
  540. $mapping['inversedBy'] = $manyToManyAnnot->inversedBy;
  541. $mapping['cascade'] = $manyToManyAnnot->cascade;
  542. $mapping['indexBy'] = $manyToManyAnnot->indexBy;
  543. $mapping['orphanRemoval'] = $manyToManyAnnot->orphanRemoval;
  544. $mapping['fetch'] = $this->getFetchMode($className, $manyToManyAnnot->fetch);
  545. $orderByAnnot = $this->reader->getPropertyAnnotation($property, Mapping\OrderBy::class);
  546. if ($orderByAnnot) {
  547. $mapping['orderBy'] = $orderByAnnot->value;
  548. }
  549. $metadata->mapManyToMany($mapping);
  550. }
  551. $embeddedAnnot = $this->reader->getPropertyAnnotation($property, Mapping\Embedded::class);
  552. if ($embeddedAnnot) {
  553. $mapping['class'] = $embeddedAnnot->class;
  554. $mapping['columnPrefix'] = $embeddedAnnot->columnPrefix;
  555. $metadata->mapEmbedded($mapping);
  556. }
  557. }
  558. /**
  559. * Attempts to resolve the fetch mode.
  560. *
  561. * @param class-string $className
  562. *
  563. * @psalm-return ClassMetadata::FETCH_* The fetch mode as defined in ClassMetadata.
  564. *
  565. * @throws MappingException If the fetch mode is not valid.
  566. */
  567. private function getFetchMode(string $className, string $fetchMode): int
  568. {
  569. if (! defined('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $fetchMode)) {
  570. throw MappingException::invalidFetchMode($className, $fetchMode);
  571. }
  572. return constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $fetchMode);
  573. }
  574. /**
  575. * Attempts to resolve the generated mode.
  576. *
  577. * @psalm-return ClassMetadata::GENERATED_*
  578. *
  579. * @throws MappingException If the fetch mode is not valid.
  580. */
  581. private function getGeneratedMode(string $generatedMode): int
  582. {
  583. if (! defined('Doctrine\ORM\Mapping\ClassMetadata::GENERATED_' . $generatedMode)) {
  584. throw MappingException::invalidGeneratedMode($generatedMode);
  585. }
  586. return constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATED_' . $generatedMode);
  587. }
  588. /**
  589. * Parses the given method.
  590. *
  591. * @return list<array{string, string}>
  592. * @psalm-return list<array{string, (Events::*)}>
  593. */
  594. private function getMethodCallbacks(ReflectionMethod $method): array
  595. {
  596. $callbacks = [];
  597. $annotations = $this->reader->getMethodAnnotations($method);
  598. foreach ($annotations as $annot) {
  599. if ($annot instanceof Mapping\PrePersist) {
  600. $callbacks[] = [$method->name, Events::prePersist];
  601. }
  602. if ($annot instanceof Mapping\PostPersist) {
  603. $callbacks[] = [$method->name, Events::postPersist];
  604. }
  605. if ($annot instanceof Mapping\PreUpdate) {
  606. $callbacks[] = [$method->name, Events::preUpdate];
  607. }
  608. if ($annot instanceof Mapping\PostUpdate) {
  609. $callbacks[] = [$method->name, Events::postUpdate];
  610. }
  611. if ($annot instanceof Mapping\PreRemove) {
  612. $callbacks[] = [$method->name, Events::preRemove];
  613. }
  614. if ($annot instanceof Mapping\PostRemove) {
  615. $callbacks[] = [$method->name, Events::postRemove];
  616. }
  617. if ($annot instanceof Mapping\PostLoad) {
  618. $callbacks[] = [$method->name, Events::postLoad];
  619. }
  620. if ($annot instanceof Mapping\PreFlush) {
  621. $callbacks[] = [$method->name, Events::preFlush];
  622. }
  623. }
  624. return $callbacks;
  625. }
  626. /**
  627. * Parse the given JoinColumn as array
  628. *
  629. * @return mixed[]
  630. * @psalm-return array{
  631. * name: string|null,
  632. * unique: bool,
  633. * nullable: bool,
  634. * onDelete: mixed,
  635. * columnDefinition: string|null,
  636. * referencedColumnName: string,
  637. * options?: array<string, mixed>
  638. * }
  639. */
  640. private function joinColumnToArray(Mapping\JoinColumn $joinColumn): array
  641. {
  642. $mapping = [
  643. 'name' => $joinColumn->name,
  644. 'unique' => $joinColumn->unique,
  645. 'nullable' => $joinColumn->nullable,
  646. 'onDelete' => $joinColumn->onDelete,
  647. 'columnDefinition' => $joinColumn->columnDefinition,
  648. 'referencedColumnName' => $joinColumn->referencedColumnName,
  649. ];
  650. if ($joinColumn->options) {
  651. $mapping['options'] = $joinColumn->options;
  652. }
  653. return $mapping;
  654. }
  655. /**
  656. * Parse the given Column as array
  657. *
  658. * @return mixed[]
  659. * @psalm-return array{
  660. * fieldName: string,
  661. * type: mixed,
  662. * scale: int,
  663. * length: int,
  664. * unique: bool,
  665. * nullable: bool,
  666. * precision: int,
  667. * notInsertable?: bool,
  668. * notUpdateble?: bool,
  669. * generated?: ClassMetadata::GENERATED_*,
  670. * enumType?: class-string,
  671. * options?: mixed[],
  672. * columnName?: string,
  673. * columnDefinition?: string
  674. * }
  675. */
  676. private function columnToArray(string $fieldName, Mapping\Column $column): array
  677. {
  678. $mapping = [
  679. 'fieldName' => $fieldName,
  680. 'type' => $column->type,
  681. 'scale' => $column->scale,
  682. 'length' => $column->length,
  683. 'unique' => $column->unique,
  684. 'nullable' => $column->nullable,
  685. 'precision' => $column->precision,
  686. ];
  687. if (! $column->insertable) {
  688. $mapping['notInsertable'] = true;
  689. }
  690. if (! $column->updatable) {
  691. $mapping['notUpdatable'] = true;
  692. }
  693. if ($column->generated) {
  694. $mapping['generated'] = $this->getGeneratedMode($column->generated);
  695. }
  696. if ($column->options) {
  697. $mapping['options'] = $column->options;
  698. }
  699. if (isset($column->name)) {
  700. $mapping['columnName'] = $column->name;
  701. }
  702. if (isset($column->columnDefinition)) {
  703. $mapping['columnDefinition'] = $column->columnDefinition;
  704. }
  705. if ($column->enumType !== null) {
  706. $mapping['enumType'] = $column->enumType;
  707. }
  708. return $mapping;
  709. }
  710. /**
  711. * Retrieve the current annotation reader
  712. *
  713. * @return Reader
  714. */
  715. public function getReader()
  716. {
  717. Deprecation::trigger(
  718. 'doctrine/orm',
  719. 'https://github.com/doctrine/orm/pull/9587',
  720. '%s is deprecated with no replacement',
  721. __METHOD__
  722. );
  723. return $this->reader;
  724. }
  725. /**
  726. * {@inheritDoc}
  727. */
  728. public function isTransient($className)
  729. {
  730. $classAnnotations = $this->reader->getClassAnnotations(new ReflectionClass($className));
  731. foreach ($classAnnotations as $annot) {
  732. if (isset($this->entityAnnotationClasses[get_class($annot)])) {
  733. return false;
  734. }
  735. }
  736. return true;
  737. }
  738. /**
  739. * Factory method for the Annotation Driver.
  740. *
  741. * @param mixed[]|string $paths
  742. *
  743. * @return AnnotationDriver
  744. */
  745. public static function create($paths = [], ?AnnotationReader $reader = null)
  746. {
  747. if ($reader === null) {
  748. $reader = new AnnotationReader();
  749. }
  750. return new self($reader, $paths);
  751. }
  752. }