vendor/doctrine/orm/lib/Doctrine/ORM/Mapping/Driver/AttributeDriver.php line 102

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