vendor/doctrine/orm/src/Mapping/Driver/AnnotationDriver.php line 94

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