vendor/doctrine/orm/src/Mapping/Driver/AttributeDriver.php line 286

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace Doctrine\ORM\Mapping\Driver;
  4. use Doctrine\ORM\Events;
  5. use Doctrine\ORM\Mapping;
  6. use Doctrine\ORM\Mapping\Builder\EntityListenerBuilder;
  7. use Doctrine\ORM\Mapping\ClassMetadata;
  8. use Doctrine\ORM\Mapping\MappingException;
  9. use Doctrine\Persistence\Mapping\ClassMetadata as PersistenceClassMetadata;
  10. use Doctrine\Persistence\Mapping\Driver\ColocatedMappingDriver;
  11. use Doctrine\Persistence\Mapping\Driver\MappingDriver;
  12. use InvalidArgumentException;
  13. use ReflectionClass;
  14. use ReflectionMethod;
  15. use ReflectionProperty;
  16. use function assert;
  17. use function class_exists;
  18. use function constant;
  19. use function defined;
  20. use function sprintf;
  21. class AttributeDriver implements MappingDriver
  22. {
  23.     use ColocatedMappingDriver;
  24.     use ReflectionBasedDriver;
  25.     private const ENTITY_ATTRIBUTE_CLASSES = [
  26.         Mapping\Entity::class => 1,
  27.         Mapping\MappedSuperclass::class => 2,
  28.     ];
  29.     private readonly AttributeReader $reader;
  30.     /**
  31.      * @param array<string> $paths
  32.      * @param true          $reportFieldsWhereDeclared no-op, to be removed in 4.0
  33.      */
  34.     public function __construct(array $pathsbool $reportFieldsWhereDeclared true)
  35.     {
  36.         if (! $reportFieldsWhereDeclared) {
  37.             throw new InvalidArgumentException(sprintf(
  38.                 'The $reportFieldsWhereDeclared argument is no longer supported, make sure to omit it when calling %s.',
  39.                 __METHOD__,
  40.             ));
  41.         }
  42.         $this->reader = new AttributeReader();
  43.         $this->addPaths($paths);
  44.     }
  45.     public function isTransient(string $className): bool
  46.     {
  47.         $classAttributes $this->reader->getClassAttributes(new ReflectionClass($className));
  48.         foreach ($classAttributes as $a) {
  49.             $attr $a instanceof RepeatableAttributeCollection $a[0] : $a;
  50.             if (isset(self::ENTITY_ATTRIBUTE_CLASSES[$attr::class])) {
  51.                 return false;
  52.             }
  53.         }
  54.         return true;
  55.     }
  56.     /**
  57.      * {@inheritDoc}
  58.      *
  59.      * @psalm-param class-string<T> $className
  60.      * @psalm-param ClassMetadata<T> $metadata
  61.      *
  62.      * @template T of object
  63.      */
  64.     public function loadMetadataForClass(string $classNamePersistenceClassMetadata $metadata): void
  65.     {
  66.         $reflectionClass $metadata->getReflectionClass()
  67.             // this happens when running attribute driver in combination with
  68.             // static reflection services. This is not the nicest fix
  69.             ?? new ReflectionClass($metadata->name);
  70.         $classAttributes $this->reader->getClassAttributes($reflectionClass);
  71.         // Evaluate Entity attribute
  72.         if (isset($classAttributes[Mapping\Entity::class])) {
  73.             $entityAttribute $classAttributes[Mapping\Entity::class];
  74.             if ($entityAttribute->repositoryClass !== null) {
  75.                 $metadata->setCustomRepositoryClass($entityAttribute->repositoryClass);
  76.             }
  77.             if ($entityAttribute->readOnly) {
  78.                 $metadata->markReadOnly();
  79.             }
  80.         } elseif (isset($classAttributes[Mapping\MappedSuperclass::class])) {
  81.             $mappedSuperclassAttribute $classAttributes[Mapping\MappedSuperclass::class];
  82.             $metadata->setCustomRepositoryClass($mappedSuperclassAttribute->repositoryClass);
  83.             $metadata->isMappedSuperclass true;
  84.         } elseif (isset($classAttributes[Mapping\Embeddable::class])) {
  85.             $metadata->isEmbeddedClass true;
  86.         } else {
  87.             throw MappingException::classIsNotAValidEntityOrMappedSuperClass($className);
  88.         }
  89.         $primaryTable = [];
  90.         if (isset($classAttributes[Mapping\Table::class])) {
  91.             $tableAnnot             $classAttributes[Mapping\Table::class];
  92.             $primaryTable['name']   = $tableAnnot->name;
  93.             $primaryTable['schema'] = $tableAnnot->schema;
  94.             if ($tableAnnot->options) {
  95.                 $primaryTable['options'] = $tableAnnot->options;
  96.             }
  97.         }
  98.         if (isset($classAttributes[Mapping\Index::class])) {
  99.             if ($metadata->isEmbeddedClass) {
  100.                 throw MappingException::invalidAttributeOnEmbeddable($metadata->nameMapping\Index::class);
  101.             }
  102.             foreach ($classAttributes[Mapping\Index::class] as $idx => $indexAnnot) {
  103.                 $index = [];
  104.                 if (! empty($indexAnnot->columns)) {
  105.                     $index['columns'] = $indexAnnot->columns;
  106.                 }
  107.                 if (! empty($indexAnnot->fields)) {
  108.                     $index['fields'] = $indexAnnot->fields;
  109.                 }
  110.                 if (
  111.                     isset($index['columns'], $index['fields'])
  112.                     || (
  113.                         ! isset($index['columns'])
  114.                         && ! isset($index['fields'])
  115.                     )
  116.                 ) {
  117.                     throw MappingException::invalidIndexConfiguration(
  118.                         $className,
  119.                         (string) ($indexAnnot->name ?? $idx),
  120.                     );
  121.                 }
  122.                 if (! empty($indexAnnot->flags)) {
  123.                     $index['flags'] = $indexAnnot->flags;
  124.                 }
  125.                 if (! empty($indexAnnot->options)) {
  126.                     $index['options'] = $indexAnnot->options;
  127.                 }
  128.                 if (! empty($indexAnnot->name)) {
  129.                     $primaryTable['indexes'][$indexAnnot->name] = $index;
  130.                 } else {
  131.                     $primaryTable['indexes'][] = $index;
  132.                 }
  133.             }
  134.         }
  135.         if (isset($classAttributes[Mapping\UniqueConstraint::class])) {
  136.             if ($metadata->isEmbeddedClass) {
  137.                 throw MappingException::invalidAttributeOnEmbeddable($metadata->nameMapping\UniqueConstraint::class);
  138.             }
  139.             foreach ($classAttributes[Mapping\UniqueConstraint::class] as $idx => $uniqueConstraintAnnot) {
  140.                 $uniqueConstraint = [];
  141.                 if (! empty($uniqueConstraintAnnot->columns)) {
  142.                     $uniqueConstraint['columns'] = $uniqueConstraintAnnot->columns;
  143.                 }
  144.                 if (! empty($uniqueConstraintAnnot->fields)) {
  145.                     $uniqueConstraint['fields'] = $uniqueConstraintAnnot->fields;
  146.                 }
  147.                 if (
  148.                     isset($uniqueConstraint['columns'], $uniqueConstraint['fields'])
  149.                     || (
  150.                         ! isset($uniqueConstraint['columns'])
  151.                         && ! isset($uniqueConstraint['fields'])
  152.                     )
  153.                 ) {
  154.                     throw MappingException::invalidUniqueConstraintConfiguration(
  155.                         $className,
  156.                         (string) ($uniqueConstraintAnnot->name ?? $idx),
  157.                     );
  158.                 }
  159.                 if (! empty($uniqueConstraintAnnot->options)) {
  160.                     $uniqueConstraint['options'] = $uniqueConstraintAnnot->options;
  161.                 }
  162.                 if (! empty($uniqueConstraintAnnot->name)) {
  163.                     $primaryTable['uniqueConstraints'][$uniqueConstraintAnnot->name] = $uniqueConstraint;
  164.                 } else {
  165.                     $primaryTable['uniqueConstraints'][] = $uniqueConstraint;
  166.                 }
  167.             }
  168.         }
  169.         $metadata->setPrimaryTable($primaryTable);
  170.         // Evaluate #[Cache] attribute
  171.         if (isset($classAttributes[Mapping\Cache::class])) {
  172.             if ($metadata->isEmbeddedClass) {
  173.                 throw MappingException::invalidAttributeOnEmbeddable($metadata->nameMapping\Cache::class);
  174.             }
  175.             $cacheAttribute $classAttributes[Mapping\Cache::class];
  176.             $cacheMap       = [
  177.                 'region' => $cacheAttribute->region,
  178.                 'usage'  => constant('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' $cacheAttribute->usage),
  179.             ];
  180.             $metadata->enableCache($cacheMap);
  181.         }
  182.         // Evaluate InheritanceType attribute
  183.         if (isset($classAttributes[Mapping\InheritanceType::class])) {
  184.             if ($metadata->isEmbeddedClass) {
  185.                 throw MappingException::invalidAttributeOnEmbeddable($metadata->nameMapping\InheritanceType::class);
  186.             }
  187.             $inheritanceTypeAttribute $classAttributes[Mapping\InheritanceType::class];
  188.             $metadata->setInheritanceType(
  189.                 constant('Doctrine\ORM\Mapping\ClassMetadata::INHERITANCE_TYPE_' $inheritanceTypeAttribute->value),
  190.             );
  191.             if ($metadata->inheritanceType !== ClassMetadata::INHERITANCE_TYPE_NONE) {
  192.                 // Evaluate DiscriminatorColumn attribute
  193.                 if (isset($classAttributes[Mapping\DiscriminatorColumn::class])) {
  194.                     $discrColumnAttribute $classAttributes[Mapping\DiscriminatorColumn::class];
  195.                     assert($discrColumnAttribute instanceof Mapping\DiscriminatorColumn);
  196.                     $columnDef = [
  197.                         'name'             => $discrColumnAttribute->name,
  198.                         'type'             => $discrColumnAttribute->type ?? 'string',
  199.                         'length'           => $discrColumnAttribute->length ?? 255,
  200.                         'columnDefinition' => $discrColumnAttribute->columnDefinition,
  201.                         'enumType'         => $discrColumnAttribute->enumType,
  202.                     ];
  203.                     if ($discrColumnAttribute->options) {
  204.                         $columnDef['options'] = $discrColumnAttribute->options;
  205.                     }
  206.                     $metadata->setDiscriminatorColumn($columnDef);
  207.                 } else {
  208.                     $metadata->setDiscriminatorColumn(['name' => 'dtype''type' => 'string''length' => 255]);
  209.                 }
  210.                 // Evaluate DiscriminatorMap attribute
  211.                 if (isset($classAttributes[Mapping\DiscriminatorMap::class])) {
  212.                     $discrMapAttribute $classAttributes[Mapping\DiscriminatorMap::class];
  213.                     $metadata->setDiscriminatorMap($discrMapAttribute->value);
  214.                 }
  215.             }
  216.         }
  217.         // Evaluate DoctrineChangeTrackingPolicy attribute
  218.         if (isset($classAttributes[Mapping\ChangeTrackingPolicy::class])) {
  219.             if ($metadata->isEmbeddedClass) {
  220.                 throw MappingException::invalidAttributeOnEmbeddable($metadata->nameMapping\ChangeTrackingPolicy::class);
  221.             }
  222.             $changeTrackingAttribute $classAttributes[Mapping\ChangeTrackingPolicy::class];
  223.             $metadata->setChangeTrackingPolicy(constant('Doctrine\ORM\Mapping\ClassMetadata::CHANGETRACKING_' $changeTrackingAttribute->value));
  224.         }
  225.         foreach ($reflectionClass->getProperties() as $property) {
  226.             assert($property instanceof ReflectionProperty);
  227.             if ($this->isRepeatedPropertyDeclaration($property$metadata)) {
  228.                 continue;
  229.             }
  230.             $mapping              = [];
  231.             $mapping['fieldName'] = $property->name;
  232.             // Evaluate #[Cache] attribute
  233.             $cacheAttribute $this->reader->getPropertyAttribute($propertyMapping\Cache::class);
  234.             if ($cacheAttribute !== null) {
  235.                 assert($cacheAttribute instanceof Mapping\Cache);
  236.                 $mapping['cache'] = $metadata->getAssociationCacheDefaults(
  237.                     $mapping['fieldName'],
  238.                     [
  239.                         'usage'  => (int) constant('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' $cacheAttribute->usage),
  240.                         'region' => $cacheAttribute->region,
  241.                     ],
  242.                 );
  243.             }
  244.             // Check for JoinColumn/JoinColumns attributes
  245.             $joinColumns = [];
  246.             $joinColumnAttributes $this->reader->getPropertyAttributeCollection($propertyMapping\JoinColumn::class);
  247.             foreach ($joinColumnAttributes as $joinColumnAttribute) {
  248.                 $joinColumns[] = $this->joinColumnToArray($joinColumnAttribute);
  249.             }
  250.             // Field can only be attributed with one of:
  251.             // Column, OneToOne, OneToMany, ManyToOne, ManyToMany, Embedded
  252.             $columnAttribute     $this->reader->getPropertyAttribute($propertyMapping\Column::class);
  253.             $oneToOneAttribute   $this->reader->getPropertyAttribute($propertyMapping\OneToOne::class);
  254.             $oneToManyAttribute  $this->reader->getPropertyAttribute($propertyMapping\OneToMany::class);
  255.             $manyToOneAttribute  $this->reader->getPropertyAttribute($propertyMapping\ManyToOne::class);
  256.             $manyToManyAttribute $this->reader->getPropertyAttribute($propertyMapping\ManyToMany::class);
  257.             $embeddedAttribute   $this->reader->getPropertyAttribute($propertyMapping\Embedded::class);
  258.             if ($columnAttribute !== null) {
  259.                 $mapping $this->columnToArray($property->name$columnAttribute);
  260.                 if ($this->reader->getPropertyAttribute($propertyMapping\Id::class)) {
  261.                     $mapping['id'] = true;
  262.                 }
  263.                 $generatedValueAttribute $this->reader->getPropertyAttribute($propertyMapping\GeneratedValue::class);
  264.                 if ($generatedValueAttribute !== null) {
  265.                     $metadata->setIdGeneratorType(constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_' $generatedValueAttribute->strategy));
  266.                 }
  267.                 if ($this->reader->getPropertyAttribute($propertyMapping\Version::class)) {
  268.                     $metadata->setVersionMapping($mapping);
  269.                 }
  270.                 $metadata->mapField($mapping);
  271.                 // Check for SequenceGenerator/TableGenerator definition
  272.                 $seqGeneratorAttribute    $this->reader->getPropertyAttribute($propertyMapping\SequenceGenerator::class);
  273.                 $customGeneratorAttribute $this->reader->getPropertyAttribute($propertyMapping\CustomIdGenerator::class);
  274.                 if ($seqGeneratorAttribute !== null) {
  275.                     $metadata->setSequenceGeneratorDefinition(
  276.                         [
  277.                             'sequenceName' => $seqGeneratorAttribute->sequenceName,
  278.                             'allocationSize' => $seqGeneratorAttribute->allocationSize,
  279.                             'initialValue' => $seqGeneratorAttribute->initialValue,
  280.                         ],
  281.                     );
  282.                 } elseif ($customGeneratorAttribute !== null) {
  283.                     $metadata->setCustomGeneratorDefinition(
  284.                         [
  285.                             'class' => $customGeneratorAttribute->class,
  286.                         ],
  287.                     );
  288.                 }
  289.             } elseif ($oneToOneAttribute !== null) {
  290.                 if ($metadata->isEmbeddedClass) {
  291.                     throw MappingException::invalidAttributeOnEmbeddable($metadata->nameMapping\OneToOne::class);
  292.                 }
  293.                 if ($this->reader->getPropertyAttribute($propertyMapping\Id::class)) {
  294.                     $mapping['id'] = true;
  295.                 }
  296.                 $mapping['targetEntity']  = $oneToOneAttribute->targetEntity;
  297.                 $mapping['joinColumns']   = $joinColumns;
  298.                 $mapping['mappedBy']      = $oneToOneAttribute->mappedBy;
  299.                 $mapping['inversedBy']    = $oneToOneAttribute->inversedBy;
  300.                 $mapping['cascade']       = $oneToOneAttribute->cascade;
  301.                 $mapping['orphanRemoval'] = $oneToOneAttribute->orphanRemoval;
  302.                 $mapping['fetch']         = $this->getFetchMode($className$oneToOneAttribute->fetch);
  303.                 $metadata->mapOneToOne($mapping);
  304.             } elseif ($oneToManyAttribute !== null) {
  305.                 if ($metadata->isEmbeddedClass) {
  306.                     throw MappingException::invalidAttributeOnEmbeddable($metadata->nameMapping\OneToMany::class);
  307.                 }
  308.                 $mapping['mappedBy']      = $oneToManyAttribute->mappedBy;
  309.                 $mapping['targetEntity']  = $oneToManyAttribute->targetEntity;
  310.                 $mapping['cascade']       = $oneToManyAttribute->cascade;
  311.                 $mapping['indexBy']       = $oneToManyAttribute->indexBy;
  312.                 $mapping['orphanRemoval'] = $oneToManyAttribute->orphanRemoval;
  313.                 $mapping['fetch']         = $this->getFetchMode($className$oneToManyAttribute->fetch);
  314.                 $orderByAttribute $this->reader->getPropertyAttribute($propertyMapping\OrderBy::class);
  315.                 if ($orderByAttribute !== null) {
  316.                     $mapping['orderBy'] = $orderByAttribute->value;
  317.                 }
  318.                 $metadata->mapOneToMany($mapping);
  319.             } elseif ($manyToOneAttribute !== null) {
  320.                 if ($metadata->isEmbeddedClass) {
  321.                     throw MappingException::invalidAttributeOnEmbeddable($metadata->nameMapping\OneToMany::class);
  322.                 }
  323.                 $idAttribute $this->reader->getPropertyAttribute($propertyMapping\Id::class);
  324.                 if ($idAttribute !== null) {
  325.                     $mapping['id'] = true;
  326.                 }
  327.                 $mapping['joinColumns']  = $joinColumns;
  328.                 $mapping['cascade']      = $manyToOneAttribute->cascade;
  329.                 $mapping['inversedBy']   = $manyToOneAttribute->inversedBy;
  330.                 $mapping['targetEntity'] = $manyToOneAttribute->targetEntity;
  331.                 $mapping['fetch']        = $this->getFetchMode($className$manyToOneAttribute->fetch);
  332.                 $metadata->mapManyToOne($mapping);
  333.             } elseif ($manyToManyAttribute !== null) {
  334.                 if ($metadata->isEmbeddedClass) {
  335.                     throw MappingException::invalidAttributeOnEmbeddable($metadata->nameMapping\ManyToMany::class);
  336.                 }
  337.                 $joinTable          = [];
  338.                 $joinTableAttribute $this->reader->getPropertyAttribute($propertyMapping\JoinTable::class);
  339.                 if ($joinTableAttribute !== null) {
  340.                     $joinTable = [
  341.                         'name' => $joinTableAttribute->name,
  342.                         'schema' => $joinTableAttribute->schema,
  343.                     ];
  344.                     if ($joinTableAttribute->options) {
  345.                         $joinTable['options'] = $joinTableAttribute->options;
  346.                     }
  347.                     foreach ($joinTableAttribute->joinColumns as $joinColumn) {
  348.                         $joinTable['joinColumns'][] = $this->joinColumnToArray($joinColumn);
  349.                     }
  350.                     foreach ($joinTableAttribute->inverseJoinColumns as $joinColumn) {
  351.                         $joinTable['inverseJoinColumns'][] = $this->joinColumnToArray($joinColumn);
  352.                     }
  353.                 }
  354.                 foreach ($this->reader->getPropertyAttributeCollection($propertyMapping\JoinColumn::class) as $joinColumn) {
  355.                     $joinTable['joinColumns'][] = $this->joinColumnToArray($joinColumn);
  356.                 }
  357.                 foreach ($this->reader->getPropertyAttributeCollection($propertyMapping\InverseJoinColumn::class) as $joinColumn) {
  358.                     $joinTable['inverseJoinColumns'][] = $this->joinColumnToArray($joinColumn);
  359.                 }
  360.                 $mapping['joinTable']     = $joinTable;
  361.                 $mapping['targetEntity']  = $manyToManyAttribute->targetEntity;
  362.                 $mapping['mappedBy']      = $manyToManyAttribute->mappedBy;
  363.                 $mapping['inversedBy']    = $manyToManyAttribute->inversedBy;
  364.                 $mapping['cascade']       = $manyToManyAttribute->cascade;
  365.                 $mapping['indexBy']       = $manyToManyAttribute->indexBy;
  366.                 $mapping['orphanRemoval'] = $manyToManyAttribute->orphanRemoval;
  367.                 $mapping['fetch']         = $this->getFetchMode($className$manyToManyAttribute->fetch);
  368.                 $orderByAttribute $this->reader->getPropertyAttribute($propertyMapping\OrderBy::class);
  369.                 if ($orderByAttribute !== null) {
  370.                     $mapping['orderBy'] = $orderByAttribute->value;
  371.                 }
  372.                 $metadata->mapManyToMany($mapping);
  373.             } elseif ($embeddedAttribute !== null) {
  374.                 $mapping['class']        = $embeddedAttribute->class;
  375.                 $mapping['columnPrefix'] = $embeddedAttribute->columnPrefix;
  376.                 $metadata->mapEmbedded($mapping);
  377.             }
  378.         }
  379.         // Evaluate AssociationOverrides attribute
  380.         if (isset($classAttributes[Mapping\AssociationOverrides::class])) {
  381.             if ($metadata->isEmbeddedClass) {
  382.                 throw MappingException::invalidAttributeOnEmbeddable($metadata->nameMapping\AssociationOverride::class);
  383.             }
  384.             $associationOverride $classAttributes[Mapping\AssociationOverrides::class];
  385.             foreach ($associationOverride->overrides as $associationOverride) {
  386.                 $override  = [];
  387.                 $fieldName $associationOverride->name;
  388.                 // Check for JoinColumn/JoinColumns attributes
  389.                 if ($associationOverride->joinColumns) {
  390.                     $joinColumns = [];
  391.                     foreach ($associationOverride->joinColumns as $joinColumn) {
  392.                         $joinColumns[] = $this->joinColumnToArray($joinColumn);
  393.                     }
  394.                     $override['joinColumns'] = $joinColumns;
  395.                 }
  396.                 if ($associationOverride->inverseJoinColumns) {
  397.                     $joinColumns = [];
  398.                     foreach ($associationOverride->inverseJoinColumns as $joinColumn) {
  399.                         $joinColumns[] = $this->joinColumnToArray($joinColumn);
  400.                     }
  401.                     $override['inverseJoinColumns'] = $joinColumns;
  402.                 }
  403.                 // Check for JoinTable attributes
  404.                 if ($associationOverride->joinTable) {
  405.                     $joinTableAnnot $associationOverride->joinTable;
  406.                     $joinTable      = [
  407.                         'name'      => $joinTableAnnot->name,
  408.                         'schema'    => $joinTableAnnot->schema,
  409.                         'joinColumns' => $override['joinColumns'] ?? [],
  410.                         'inverseJoinColumns' => $override['inverseJoinColumns'] ?? [],
  411.                     ];
  412.                     unset($override['joinColumns'], $override['inverseJoinColumns']);
  413.                     $override['joinTable'] = $joinTable;
  414.                 }
  415.                 // Check for inversedBy
  416.                 if ($associationOverride->inversedBy) {
  417.                     $override['inversedBy'] = $associationOverride->inversedBy;
  418.                 }
  419.                 // Check for `fetch`
  420.                 if ($associationOverride->fetch) {
  421.                     $override['fetch'] = constant(ClassMetadata::class . '::FETCH_' $associationOverride->fetch);
  422.                 }
  423.                 $metadata->setAssociationOverride($fieldName$override);
  424.             }
  425.         }
  426.         // Evaluate AttributeOverrides attribute
  427.         if (isset($classAttributes[Mapping\AttributeOverrides::class])) {
  428.             if ($metadata->isEmbeddedClass) {
  429.                 throw MappingException::invalidAttributeOnEmbeddable($metadata->nameMapping\AttributeOverrides::class);
  430.             }
  431.             $attributeOverridesAnnot $classAttributes[Mapping\AttributeOverrides::class];
  432.             foreach ($attributeOverridesAnnot->overrides as $attributeOverride) {
  433.                 $mapping $this->columnToArray($attributeOverride->name$attributeOverride->column);
  434.                 $metadata->setAttributeOverride($attributeOverride->name$mapping);
  435.             }
  436.         }
  437.         // Evaluate EntityListeners attribute
  438.         if (isset($classAttributes[Mapping\EntityListeners::class])) {
  439.             if ($metadata->isEmbeddedClass) {
  440.                 throw MappingException::invalidAttributeOnEmbeddable($metadata->nameMapping\EntityListeners::class);
  441.             }
  442.             $entityListenersAttribute $classAttributes[Mapping\EntityListeners::class];
  443.             foreach ($entityListenersAttribute->value as $item) {
  444.                 $listenerClassName $metadata->fullyQualifiedClassName($item);
  445.                 if (! class_exists($listenerClassName)) {
  446.                     throw MappingException::entityListenerClassNotFound($listenerClassName$className);
  447.                 }
  448.                 $hasMapping    false;
  449.                 $listenerClass = new ReflectionClass($listenerClassName);
  450.                 foreach ($listenerClass->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
  451.                     assert($method instanceof ReflectionMethod);
  452.                     // find method callbacks.
  453.                     $callbacks  $this->getMethodCallbacks($method);
  454.                     $hasMapping $hasMapping ?: ! empty($callbacks);
  455.                     foreach ($callbacks as $value) {
  456.                         $metadata->addEntityListener($value[1], $listenerClassName$value[0]);
  457.                     }
  458.                 }
  459.                 // Evaluate the listener using naming convention.
  460.                 if (! $hasMapping) {
  461.                     EntityListenerBuilder::bindEntityListener($metadata$listenerClassName);
  462.                 }
  463.             }
  464.         }
  465.         // Evaluate #[HasLifecycleCallbacks] attribute
  466.         if (isset($classAttributes[Mapping\HasLifecycleCallbacks::class])) {
  467.             if ($metadata->isEmbeddedClass) {
  468.                 throw MappingException::invalidAttributeOnEmbeddable($metadata->nameMapping\HasLifecycleCallbacks::class);
  469.             }
  470.             foreach ($reflectionClass->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
  471.                 assert($method instanceof ReflectionMethod);
  472.                 foreach ($this->getMethodCallbacks($method) as $value) {
  473.                     $metadata->addLifecycleCallback($value[0], $value[1]);
  474.                 }
  475.             }
  476.         }
  477.     }
  478.     /**
  479.      * Attempts to resolve the fetch mode.
  480.      *
  481.      * @param class-string $className The class name.
  482.      * @param string       $fetchMode The fetch mode.
  483.      *
  484.      * @return ClassMetadata::FETCH_* The fetch mode as defined in ClassMetadata.
  485.      *
  486.      * @throws MappingException If the fetch mode is not valid.
  487.      */
  488.     private function getFetchMode(string $classNamestring $fetchMode): int
  489.     {
  490.         if (! defined('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' $fetchMode)) {
  491.             throw MappingException::invalidFetchMode($className$fetchMode);
  492.         }
  493.         return constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' $fetchMode);
  494.     }
  495.     /**
  496.      * Attempts to resolve the generated mode.
  497.      *
  498.      * @throws MappingException If the fetch mode is not valid.
  499.      */
  500.     private function getGeneratedMode(string $generatedMode): int
  501.     {
  502.         if (! defined('Doctrine\ORM\Mapping\ClassMetadata::GENERATED_' $generatedMode)) {
  503.             throw MappingException::invalidGeneratedMode($generatedMode);
  504.         }
  505.         return constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATED_' $generatedMode);
  506.     }
  507.     /**
  508.      * Parses the given method.
  509.      *
  510.      * @return list<array{string, string}>
  511.      * @psalm-return list<array{string, (Events::*)}>
  512.      */
  513.     private function getMethodCallbacks(ReflectionMethod $method): array
  514.     {
  515.         $callbacks  = [];
  516.         $attributes $this->reader->getMethodAttributes($method);
  517.         foreach ($attributes as $attribute) {
  518.             if ($attribute instanceof Mapping\PrePersist) {
  519.                 $callbacks[] = [$method->nameEvents::prePersist];
  520.             }
  521.             if ($attribute instanceof Mapping\PostPersist) {
  522.                 $callbacks[] = [$method->nameEvents::postPersist];
  523.             }
  524.             if ($attribute instanceof Mapping\PreUpdate) {
  525.                 $callbacks[] = [$method->nameEvents::preUpdate];
  526.             }
  527.             if ($attribute instanceof Mapping\PostUpdate) {
  528.                 $callbacks[] = [$method->nameEvents::postUpdate];
  529.             }
  530.             if ($attribute instanceof Mapping\PreRemove) {
  531.                 $callbacks[] = [$method->nameEvents::preRemove];
  532.             }
  533.             if ($attribute instanceof Mapping\PostRemove) {
  534.                 $callbacks[] = [$method->nameEvents::postRemove];
  535.             }
  536.             if ($attribute instanceof Mapping\PostLoad) {
  537.                 $callbacks[] = [$method->nameEvents::postLoad];
  538.             }
  539.             if ($attribute instanceof Mapping\PreFlush) {
  540.                 $callbacks[] = [$method->nameEvents::preFlush];
  541.             }
  542.         }
  543.         return $callbacks;
  544.     }
  545.     /**
  546.      * Parse the given JoinColumn as array
  547.      *
  548.      * @return mixed[]
  549.      * @psalm-return array{
  550.      *                   name: string|null,
  551.      *                   unique: bool,
  552.      *                   nullable: bool,
  553.      *                   onDelete: mixed,
  554.      *                   columnDefinition: string|null,
  555.      *                   referencedColumnName: string,
  556.      *                   options?: array<string, mixed>
  557.      *               }
  558.      */
  559.     private function joinColumnToArray(Mapping\JoinColumn|Mapping\InverseJoinColumn $joinColumn): array
  560.     {
  561.         $mapping = [
  562.             'name' => $joinColumn->name,
  563.             'unique' => $joinColumn->unique,
  564.             'nullable' => $joinColumn->nullable,
  565.             'onDelete' => $joinColumn->onDelete,
  566.             'columnDefinition' => $joinColumn->columnDefinition,
  567.             'referencedColumnName' => $joinColumn->referencedColumnName,
  568.         ];
  569.         if ($joinColumn->options) {
  570.             $mapping['options'] = $joinColumn->options;
  571.         }
  572.         return $mapping;
  573.     }
  574.     /**
  575.      * Parse the given Column as array
  576.      *
  577.      * @return mixed[]
  578.      * @psalm-return array{
  579.      *                   fieldName: string,
  580.      *                   type: mixed,
  581.      *                   scale: int,
  582.      *                   length: int,
  583.      *                   unique: bool,
  584.      *                   nullable: bool,
  585.      *                   precision: int,
  586.      *                   enumType?: class-string,
  587.      *                   options?: mixed[],
  588.      *                   columnName?: string,
  589.      *                   columnDefinition?: string
  590.      *               }
  591.      */
  592.     private function columnToArray(string $fieldNameMapping\Column $column): array
  593.     {
  594.         $mapping = [
  595.             'fieldName' => $fieldName,
  596.             'type'      => $column->type,
  597.             'scale'     => $column->scale,
  598.             'length'    => $column->length,
  599.             'unique'    => $column->unique,
  600.             'nullable'  => $column->nullable,
  601.             'precision' => $column->precision,
  602.         ];
  603.         if ($column->options) {
  604.             $mapping['options'] = $column->options;
  605.         }
  606.         if (isset($column->name)) {
  607.             $mapping['columnName'] = $column->name;
  608.         }
  609.         if (isset($column->columnDefinition)) {
  610.             $mapping['columnDefinition'] = $column->columnDefinition;
  611.         }
  612.         if ($column->updatable === false) {
  613.             $mapping['notUpdatable'] = true;
  614.         }
  615.         if ($column->insertable === false) {
  616.             $mapping['notInsertable'] = true;
  617.         }
  618.         if ($column->generated !== null) {
  619.             $mapping['generated'] = $this->getGeneratedMode($column->generated);
  620.         }
  621.         if ($column->enumType) {
  622.             $mapping['enumType'] = $column->enumType;
  623.         }
  624.         return $mapping;
  625.     }
  626. }