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

Open in your IDE?
  1. <?php
  2. /*
  3.  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  4.  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  5.  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  6.  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  7.  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  8.  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  9.  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  10.  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  11.  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  12.  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  13.  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  14.  *
  15.  * This software consists of voluntary contributions made by many individuals
  16.  * and is licensed under the MIT license. For more information, see
  17.  * <http://www.doctrine-project.org>.
  18.  */
  19. namespace Doctrine\ORM\Mapping\Driver;
  20. use Doctrine\Common\Annotations\AnnotationReader;
  21. use Doctrine\ORM\Events;
  22. use Doctrine\ORM\Id\TableGenerator;
  23. use Doctrine\ORM\Mapping;
  24. use Doctrine\ORM\Mapping\Builder\EntityListenerBuilder;
  25. use Doctrine\ORM\Mapping\ClassMetadataInfo;
  26. use Doctrine\ORM\Mapping\MappingException;
  27. use Doctrine\Persistence\Mapping\ClassMetadata;
  28. use Doctrine\Persistence\Mapping\Driver\AnnotationDriver as AbstractAnnotationDriver;
  29. use ReflectionClass;
  30. use ReflectionMethod;
  31. use ReflectionProperty;
  32. use UnexpectedValueException;
  33. use function class_exists;
  34. use function constant;
  35. use function defined;
  36. use function get_class;
  37. use function is_array;
  38. use function is_numeric;
  39. /**
  40.  * The AnnotationDriver reads the mapping metadata from docblock annotations.
  41.  */
  42. class AnnotationDriver extends AbstractAnnotationDriver
  43. {
  44.     /**
  45.      * @var int[]
  46.      * @psalm-var array<class-string, int>
  47.      */
  48.     protected $entityAnnotationClasses = [
  49.         Mapping\Entity::class => 1,
  50.         Mapping\MappedSuperclass::class => 2,
  51.     ];
  52.     /**
  53.      * {@inheritDoc}
  54.      */
  55.     public function loadMetadataForClass($classNameClassMetadata $metadata)
  56.     {
  57.         /** @var ClassMetadataInfo $metadata */
  58.         $class $metadata->getReflectionClass();
  59.         if (! $class) {
  60.             // this happens when running annotation driver in combination with
  61.             // static reflection services. This is not the nicest fix
  62.             $class = new ReflectionClass($metadata->name);
  63.         }
  64.         $classAnnotations $this->reader->getClassAnnotations($class);
  65.         if ($classAnnotations) {
  66.             foreach ($classAnnotations as $key => $annot) {
  67.                 if (! is_numeric($key)) {
  68.                     continue;
  69.                 }
  70.                 $classAnnotations[get_class($annot)] = $annot;
  71.             }
  72.         }
  73.         // Evaluate Entity annotation
  74.         if (isset($classAnnotations[Mapping\Entity::class])) {
  75.             $entityAnnot $classAnnotations[Mapping\Entity::class];
  76.             if ($entityAnnot->repositoryClass !== null) {
  77.                 $metadata->setCustomRepositoryClass($entityAnnot->repositoryClass);
  78.             }
  79.             if ($entityAnnot->readOnly) {
  80.                 $metadata->markReadOnly();
  81.             }
  82.         } elseif (isset($classAnnotations[Mapping\MappedSuperclass::class])) {
  83.             $mappedSuperclassAnnot $classAnnotations[Mapping\MappedSuperclass::class];
  84.             $metadata->setCustomRepositoryClass($mappedSuperclassAnnot->repositoryClass);
  85.             $metadata->isMappedSuperclass true;
  86.         } elseif (isset($classAnnotations[Mapping\Embeddable::class])) {
  87.             $metadata->isEmbeddedClass true;
  88.         } else {
  89.             throw MappingException::classIsNotAValidEntityOrMappedSuperClass($className);
  90.         }
  91.         // Evaluate Table annotation
  92.         if (isset($classAnnotations[Mapping\Table::class])) {
  93.             $tableAnnot   $classAnnotations[Mapping\Table::class];
  94.             $primaryTable = [
  95.                 'name'   => $tableAnnot->name,
  96.                 'schema' => $tableAnnot->schema,
  97.             ];
  98.             if ($tableAnnot->indexes !== null) {
  99.                 foreach ($tableAnnot->indexes as $indexAnnot) {
  100.                     $index = ['columns' => $indexAnnot->columns];
  101.                     if (! empty($indexAnnot->flags)) {
  102.                         $index['flags'] = $indexAnnot->flags;
  103.                     }
  104.                     if (! empty($indexAnnot->options)) {
  105.                         $index['options'] = $indexAnnot->options;
  106.                     }
  107.                     if (! empty($indexAnnot->name)) {
  108.                         $primaryTable['indexes'][$indexAnnot->name] = $index;
  109.                     } else {
  110.                         $primaryTable['indexes'][] = $index;
  111.                     }
  112.                 }
  113.             }
  114.             if ($tableAnnot->uniqueConstraints !== null) {
  115.                 foreach ($tableAnnot->uniqueConstraints as $uniqueConstraintAnnot) {
  116.                     $uniqueConstraint = ['columns' => $uniqueConstraintAnnot->columns];
  117.                     if (! empty($uniqueConstraintAnnot->options)) {
  118.                         $uniqueConstraint['options'] = $uniqueConstraintAnnot->options;
  119.                     }
  120.                     if (! empty($uniqueConstraintAnnot->name)) {
  121.                         $primaryTable['uniqueConstraints'][$uniqueConstraintAnnot->name] = $uniqueConstraint;
  122.                     } else {
  123.                         $primaryTable['uniqueConstraints'][] = $uniqueConstraint;
  124.                     }
  125.                 }
  126.             }
  127.             if ($tableAnnot->options) {
  128.                 $primaryTable['options'] = $tableAnnot->options;
  129.             }
  130.             $metadata->setPrimaryTable($primaryTable);
  131.         }
  132.         // Evaluate @Cache annotation
  133.         if (isset($classAnnotations[Mapping\Cache::class])) {
  134.             $cacheAnnot $classAnnotations[Mapping\Cache::class];
  135.             $cacheMap   = [
  136.                 'region' => $cacheAnnot->region,
  137.                 'usage'  => constant('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' $cacheAnnot->usage),
  138.             ];
  139.             $metadata->enableCache($cacheMap);
  140.         }
  141.         // Evaluate NamedNativeQueries annotation
  142.         if (isset($classAnnotations[Mapping\NamedNativeQueries::class])) {
  143.             $namedNativeQueriesAnnot $classAnnotations[Mapping\NamedNativeQueries::class];
  144.             foreach ($namedNativeQueriesAnnot->value as $namedNativeQuery) {
  145.                 $metadata->addNamedNativeQuery(
  146.                     [
  147.                         'name'              => $namedNativeQuery->name,
  148.                         'query'             => $namedNativeQuery->query,
  149.                         'resultClass'       => $namedNativeQuery->resultClass,
  150.                         'resultSetMapping'  => $namedNativeQuery->resultSetMapping,
  151.                     ]
  152.                 );
  153.             }
  154.         }
  155.         // Evaluate SqlResultSetMappings annotation
  156.         if (isset($classAnnotations[Mapping\SqlResultSetMappings::class])) {
  157.             $sqlResultSetMappingsAnnot $classAnnotations[Mapping\SqlResultSetMappings::class];
  158.             foreach ($sqlResultSetMappingsAnnot->value as $resultSetMapping) {
  159.                 $entities = [];
  160.                 $columns  = [];
  161.                 foreach ($resultSetMapping->entities as $entityResultAnnot) {
  162.                     $entityResult = [
  163.                         'fields'                => [],
  164.                         'entityClass'           => $entityResultAnnot->entityClass,
  165.                         'discriminatorColumn'   => $entityResultAnnot->discriminatorColumn,
  166.                     ];
  167.                     foreach ($entityResultAnnot->fields as $fieldResultAnnot) {
  168.                         $entityResult['fields'][] = [
  169.                             'name'      => $fieldResultAnnot->name,
  170.                             'column'    => $fieldResultAnnot->column,
  171.                         ];
  172.                     }
  173.                     $entities[] = $entityResult;
  174.                 }
  175.                 foreach ($resultSetMapping->columns as $columnResultAnnot) {
  176.                     $columns[] = [
  177.                         'name' => $columnResultAnnot->name,
  178.                     ];
  179.                 }
  180.                 $metadata->addSqlResultSetMapping(
  181.                     [
  182.                         'name'          => $resultSetMapping->name,
  183.                         'entities'      => $entities,
  184.                         'columns'       => $columns,
  185.                     ]
  186.                 );
  187.             }
  188.         }
  189.         // Evaluate NamedQueries annotation
  190.         if (isset($classAnnotations[Mapping\NamedQueries::class])) {
  191.             $namedQueriesAnnot $classAnnotations[Mapping\NamedQueries::class];
  192.             if (! is_array($namedQueriesAnnot->value)) {
  193.                 throw new UnexpectedValueException('@NamedQueries should contain an array of @NamedQuery annotations.');
  194.             }
  195.             foreach ($namedQueriesAnnot->value as $namedQuery) {
  196.                 if (! ($namedQuery instanceof Mapping\NamedQuery)) {
  197.                     throw new UnexpectedValueException('@NamedQueries should contain an array of @NamedQuery annotations.');
  198.                 }
  199.                 $metadata->addNamedQuery(
  200.                     [
  201.                         'name'  => $namedQuery->name,
  202.                         'query' => $namedQuery->query,
  203.                     ]
  204.                 );
  205.             }
  206.         }
  207.         // Evaluate InheritanceType annotation
  208.         if (isset($classAnnotations[Mapping\InheritanceType::class])) {
  209.             $inheritanceTypeAnnot $classAnnotations[Mapping\InheritanceType::class];
  210.             $metadata->setInheritanceType(
  211.                 constant('Doctrine\ORM\Mapping\ClassMetadata::INHERITANCE_TYPE_' $inheritanceTypeAnnot->value)
  212.             );
  213.             if ($metadata->inheritanceType !== Mapping\ClassMetadata::INHERITANCE_TYPE_NONE) {
  214.                 // Evaluate DiscriminatorColumn annotation
  215.                 if (isset($classAnnotations[Mapping\DiscriminatorColumn::class])) {
  216.                     $discrColumnAnnot $classAnnotations[Mapping\DiscriminatorColumn::class];
  217.                     $metadata->setDiscriminatorColumn(
  218.                         [
  219.                             'name'             => $discrColumnAnnot->name,
  220.                             'type'             => $discrColumnAnnot->type ?: 'string',
  221.                             'length'           => $discrColumnAnnot->length ?: 255,
  222.                             'columnDefinition' => $discrColumnAnnot->columnDefinition,
  223.                         ]
  224.                     );
  225.                 } else {
  226.                     $metadata->setDiscriminatorColumn(['name' => 'dtype''type' => 'string''length' => 255]);
  227.                 }
  228.                 // Evaluate DiscriminatorMap annotation
  229.                 if (isset($classAnnotations[Mapping\DiscriminatorMap::class])) {
  230.                     $discrMapAnnot $classAnnotations[Mapping\DiscriminatorMap::class];
  231.                     $metadata->setDiscriminatorMap($discrMapAnnot->value);
  232.                 }
  233.             }
  234.         }
  235.         // Evaluate DoctrineChangeTrackingPolicy annotation
  236.         if (isset($classAnnotations[Mapping\ChangeTrackingPolicy::class])) {
  237.             $changeTrackingAnnot $classAnnotations[Mapping\ChangeTrackingPolicy::class];
  238.             $metadata->setChangeTrackingPolicy(constant('Doctrine\ORM\Mapping\ClassMetadata::CHANGETRACKING_' $changeTrackingAnnot->value));
  239.         }
  240.         // Evaluate annotations on properties/fields
  241.         foreach ($class->getProperties() as $property) {
  242.             if (
  243.                 $metadata->isMappedSuperclass && ! $property->isPrivate()
  244.                 ||
  245.                 $metadata->isInheritedField($property->name)
  246.                 ||
  247.                 $metadata->isInheritedAssociation($property->name)
  248.                 ||
  249.                 $metadata->isInheritedEmbeddedClass($property->name)
  250.             ) {
  251.                 continue;
  252.             }
  253.             $mapping              = [];
  254.             $mapping['fieldName'] = $property->getName();
  255.             // Evaluate @Cache annotation
  256.             if (($cacheAnnot $this->reader->getPropertyAnnotation($propertyMapping\Cache::class)) !== null) {
  257.                 $mapping['cache'] = $metadata->getAssociationCacheDefaults(
  258.                     $mapping['fieldName'],
  259.                     [
  260.                         'usage'  => constant('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' $cacheAnnot->usage),
  261.                         'region' => $cacheAnnot->region,
  262.                     ]
  263.                 );
  264.             }
  265.             // Check for JoinColumn/JoinColumns annotations
  266.             $joinColumns = [];
  267.             if ($joinColumnAnnot $this->reader->getPropertyAnnotation($propertyMapping\JoinColumn::class)) {
  268.                 $joinColumns[] = $this->joinColumnToArray($joinColumnAnnot);
  269.             } else {
  270.                 $joinColumnsAnnot $this->reader->getPropertyAnnotation($propertyMapping\JoinColumns::class);
  271.                 if ($joinColumnsAnnot) {
  272.                     foreach ($joinColumnsAnnot->value as $joinColumn) {
  273.                         $joinColumns[] = $this->joinColumnToArray($joinColumn);
  274.                     }
  275.                 }
  276.             }
  277.             // Field can only be annotated with one of:
  278.             // @Column, @OneToOne, @OneToMany, @ManyToOne, @ManyToMany
  279.             if ($columnAnnot $this->reader->getPropertyAnnotation($propertyMapping\Column::class)) {
  280.                 if ($columnAnnot->type === null) {
  281.                     throw MappingException::propertyTypeIsRequired($className$property->getName());
  282.                 }
  283.                 $mapping $this->columnToArray($property->getName(), $columnAnnot);
  284.                 if ($idAnnot $this->reader->getPropertyAnnotation($propertyMapping\Id::class)) {
  285.                     $mapping['id'] = true;
  286.                 }
  287.                 if ($generatedValueAnnot $this->reader->getPropertyAnnotation($propertyMapping\GeneratedValue::class)) {
  288.                     $metadata->setIdGeneratorType(constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_' $generatedValueAnnot->strategy));
  289.                 }
  290.                 if ($this->reader->getPropertyAnnotation($propertyMapping\Version::class)) {
  291.                     $metadata->setVersionMapping($mapping);
  292.                 }
  293.                 $metadata->mapField($mapping);
  294.                 // Check for SequenceGenerator/TableGenerator definition
  295.                 if ($seqGeneratorAnnot $this->reader->getPropertyAnnotation($propertyMapping\SequenceGenerator::class)) {
  296.                     $metadata->setSequenceGeneratorDefinition(
  297.                         [
  298.                             'sequenceName' => $seqGeneratorAnnot->sequenceName,
  299.                             'allocationSize' => $seqGeneratorAnnot->allocationSize,
  300.                             'initialValue' => $seqGeneratorAnnot->initialValue,
  301.                         ]
  302.                     );
  303.                 } elseif ($this->reader->getPropertyAnnotation($propertyTableGenerator::class)) {
  304.                     throw MappingException::tableIdGeneratorNotImplemented($className);
  305.                 } else {
  306.                     $customGeneratorAnnot $this->reader->getPropertyAnnotation($propertyMapping\CustomIdGenerator::class);
  307.                     if ($customGeneratorAnnot) {
  308.                         $metadata->setCustomGeneratorDefinition(
  309.                             [
  310.                                 'class' => $customGeneratorAnnot->class,
  311.                             ]
  312.                         );
  313.                     }
  314.                 }
  315.             } else {
  316.                 $this->loadRelationShipMapping(
  317.                     $property,
  318.                     $mapping,
  319.                     $metadata,
  320.                     $joinColumns,
  321.                     $className
  322.                 );
  323.             }
  324.         }
  325.         // Evaluate AssociationOverrides annotation
  326.         if (isset($classAnnotations[Mapping\AssociationOverrides::class])) {
  327.             $associationOverridesAnnot $classAnnotations[Mapping\AssociationOverrides::class];
  328.             foreach ($associationOverridesAnnot->value as $associationOverride) {
  329.                 $override  = [];
  330.                 $fieldName $associationOverride->name;
  331.                 // Check for JoinColumn/JoinColumns annotations
  332.                 if ($associationOverride->joinColumns) {
  333.                     $joinColumns = [];
  334.                     foreach ($associationOverride->joinColumns as $joinColumn) {
  335.                         $joinColumns[] = $this->joinColumnToArray($joinColumn);
  336.                     }
  337.                     $override['joinColumns'] = $joinColumns;
  338.                 }
  339.                 // Check for JoinTable annotations
  340.                 if ($associationOverride->joinTable) {
  341.                     $joinTableAnnot $associationOverride->joinTable;
  342.                     $joinTable      = [
  343.                         'name'      => $joinTableAnnot->name,
  344.                         'schema'    => $joinTableAnnot->schema,
  345.                     ];
  346.                     foreach ($joinTableAnnot->joinColumns as $joinColumn) {
  347.                         $joinTable['joinColumns'][] = $this->joinColumnToArray($joinColumn);
  348.                     }
  349.                     foreach ($joinTableAnnot->inverseJoinColumns as $joinColumn) {
  350.                         $joinTable['inverseJoinColumns'][] = $this->joinColumnToArray($joinColumn);
  351.                     }
  352.                     $override['joinTable'] = $joinTable;
  353.                 }
  354.                 // Check for inversedBy
  355.                 if ($associationOverride->inversedBy) {
  356.                     $override['inversedBy'] = $associationOverride->inversedBy;
  357.                 }
  358.                 // Check for `fetch`
  359.                 if ($associationOverride->fetch) {
  360.                     $override['fetch'] = constant(Mapping\ClassMetadata::class . '::FETCH_' $associationOverride->fetch);
  361.                 }
  362.                 $metadata->setAssociationOverride($fieldName$override);
  363.             }
  364.         }
  365.         // Evaluate AttributeOverrides annotation
  366.         if (isset($classAnnotations[Mapping\AttributeOverrides::class])) {
  367.             $attributeOverridesAnnot $classAnnotations[Mapping\AttributeOverrides::class];
  368.             foreach ($attributeOverridesAnnot->value as $attributeOverrideAnnot) {
  369.                 $attributeOverride $this->columnToArray($attributeOverrideAnnot->name$attributeOverrideAnnot->column);
  370.                 $metadata->setAttributeOverride($attributeOverrideAnnot->name$attributeOverride);
  371.             }
  372.         }
  373.         // Evaluate EntityListeners annotation
  374.         if (isset($classAnnotations[Mapping\EntityListeners::class])) {
  375.             $entityListenersAnnot $classAnnotations[Mapping\EntityListeners::class];
  376.             foreach ($entityListenersAnnot->value as $item) {
  377.                 $listenerClassName $metadata->fullyQualifiedClassName($item);
  378.                 if (! class_exists($listenerClassName)) {
  379.                     throw MappingException::entityListenerClassNotFound($listenerClassName$className);
  380.                 }
  381.                 $hasMapping    false;
  382.                 $listenerClass = new ReflectionClass($listenerClassName);
  383.                 foreach ($listenerClass->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
  384.                     // find method callbacks.
  385.                     $callbacks  $this->getMethodCallbacks($method);
  386.                     $hasMapping $hasMapping ?: ! empty($callbacks);
  387.                     foreach ($callbacks as $value) {
  388.                         $metadata->addEntityListener($value[1], $listenerClassName$value[0]);
  389.                     }
  390.                 }
  391.                 // Evaluate the listener using naming convention.
  392.                 if (! $hasMapping) {
  393.                     EntityListenerBuilder::bindEntityListener($metadata$listenerClassName);
  394.                 }
  395.             }
  396.         }
  397.         // Evaluate @HasLifecycleCallbacks annotation
  398.         if (isset($classAnnotations[Mapping\HasLifecycleCallbacks::class])) {
  399.             foreach ($class->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
  400.                 foreach ($this->getMethodCallbacks($method) as $value) {
  401.                     $metadata->addLifecycleCallback($value[0], $value[1]);
  402.                 }
  403.             }
  404.         }
  405.     }
  406.     /**
  407.      * @param mixed[] $mapping
  408.      * @param mixed[] $joinColumns
  409.      */
  410.     private function loadRelationShipMapping(
  411.         ReflectionProperty $property,
  412.         array &$mapping,
  413.         ClassMetadata $metadata,
  414.         array $joinColumns,
  415.         string $className
  416.     ): void {
  417.         $oneToOneAnnot $this->reader->getPropertyAnnotation($propertyMapping\OneToOne::class);
  418.         if ($oneToOneAnnot) {
  419.             $idAnnot $this->reader->getPropertyAnnotation($propertyMapping\Id::class);
  420.             if ($idAnnot) {
  421.                 $mapping['id'] = true;
  422.             }
  423.             $mapping['targetEntity']  = $oneToOneAnnot->targetEntity;
  424.             $mapping['joinColumns']   = $joinColumns;
  425.             $mapping['mappedBy']      = $oneToOneAnnot->mappedBy;
  426.             $mapping['inversedBy']    = $oneToOneAnnot->inversedBy;
  427.             $mapping['cascade']       = $oneToOneAnnot->cascade;
  428.             $mapping['orphanRemoval'] = $oneToOneAnnot->orphanRemoval;
  429.             $mapping['fetch']         = $this->getFetchMode($className$oneToOneAnnot->fetch);
  430.             $metadata->mapOneToOne($mapping);
  431.             return;
  432.         }
  433.         $oneToManyAnnot $this->reader->getPropertyAnnotation($propertyMapping\OneToMany::class);
  434.         if ($oneToManyAnnot) {
  435.             $mapping['mappedBy']      = $oneToManyAnnot->mappedBy;
  436.             $mapping['targetEntity']  = $oneToManyAnnot->targetEntity;
  437.             $mapping['cascade']       = $oneToManyAnnot->cascade;
  438.             $mapping['indexBy']       = $oneToManyAnnot->indexBy;
  439.             $mapping['orphanRemoval'] = $oneToManyAnnot->orphanRemoval;
  440.             $mapping['fetch']         = $this->getFetchMode($className$oneToManyAnnot->fetch);
  441.             $orderByAnnot $this->reader->getPropertyAnnotation($propertyMapping\OrderBy::class);
  442.             if ($orderByAnnot) {
  443.                 $mapping['orderBy'] = $orderByAnnot->value;
  444.             }
  445.             $metadata->mapOneToMany($mapping);
  446.         }
  447.         $manyToOneAnnot $this->reader->getPropertyAnnotation($propertyMapping\ManyToOne::class);
  448.         if ($manyToOneAnnot) {
  449.             $idAnnot $this->reader->getPropertyAnnotation($propertyMapping\Id::class);
  450.             if ($idAnnot) {
  451.                 $mapping['id'] = true;
  452.             }
  453.             $mapping['joinColumns']  = $joinColumns;
  454.             $mapping['cascade']      = $manyToOneAnnot->cascade;
  455.             $mapping['inversedBy']   = $manyToOneAnnot->inversedBy;
  456.             $mapping['targetEntity'] = $manyToOneAnnot->targetEntity;
  457.             $mapping['fetch']        = $this->getFetchMode($className$manyToOneAnnot->fetch);
  458.             $metadata->mapManyToOne($mapping);
  459.         }
  460.         $manyToManyAnnot $this->reader->getPropertyAnnotation($propertyMapping\ManyToMany::class);
  461.         if ($manyToManyAnnot) {
  462.             $joinTable = [];
  463.             $joinTableAnnot $this->reader->getPropertyAnnotation($propertyMapping\JoinTable::class);
  464.             if ($joinTableAnnot) {
  465.                 $joinTable = [
  466.                     'name' => $joinTableAnnot->name,
  467.                     'schema' => $joinTableAnnot->schema,
  468.                 ];
  469.                 foreach ($joinTableAnnot->joinColumns as $joinColumn) {
  470.                     $joinTable['joinColumns'][] = $this->joinColumnToArray($joinColumn);
  471.                 }
  472.                 foreach ($joinTableAnnot->inverseJoinColumns as $joinColumn) {
  473.                     $joinTable['inverseJoinColumns'][] = $this->joinColumnToArray($joinColumn);
  474.                 }
  475.             }
  476.             $mapping['joinTable']     = $joinTable;
  477.             $mapping['targetEntity']  = $manyToManyAnnot->targetEntity;
  478.             $mapping['mappedBy']      = $manyToManyAnnot->mappedBy;
  479.             $mapping['inversedBy']    = $manyToManyAnnot->inversedBy;
  480.             $mapping['cascade']       = $manyToManyAnnot->cascade;
  481.             $mapping['indexBy']       = $manyToManyAnnot->indexBy;
  482.             $mapping['orphanRemoval'] = $manyToManyAnnot->orphanRemoval;
  483.             $mapping['fetch']         = $this->getFetchMode($className$manyToManyAnnot->fetch);
  484.             $orderByAnnot $this->reader->getPropertyAnnotation($propertyMapping\OrderBy::class);
  485.             if ($orderByAnnot) {
  486.                 $mapping['orderBy'] = $orderByAnnot->value;
  487.             }
  488.             $metadata->mapManyToMany($mapping);
  489.         }
  490.         $embeddedAnnot $this->reader->getPropertyAnnotation($propertyMapping\Embedded::class);
  491.         if ($embeddedAnnot) {
  492.             $mapping['class']        = $embeddedAnnot->class;
  493.             $mapping['columnPrefix'] = $embeddedAnnot->columnPrefix;
  494.             $metadata->mapEmbedded($mapping);
  495.         }
  496.     }
  497.     /**
  498.      * Attempts to resolve the fetch mode.
  499.      *
  500.      * @param string $className The class name.
  501.      * @param string $fetchMode The fetch mode.
  502.      *
  503.      * @return int The fetch mode as defined in ClassMetadata.
  504.      *
  505.      * @throws MappingException If the fetch mode is not valid.
  506.      */
  507.     private function getFetchMode($className$fetchMode)
  508.     {
  509.         if (! defined('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' $fetchMode)) {
  510.             throw MappingException::invalidFetchMode($className$fetchMode);
  511.         }
  512.         return constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' $fetchMode);
  513.     }
  514.     /**
  515.      * Parses the given method.
  516.      *
  517.      * @return callable[]
  518.      */
  519.     private function getMethodCallbacks(ReflectionMethod $method)
  520.     {
  521.         $callbacks   = [];
  522.         $annotations $this->reader->getMethodAnnotations($method);
  523.         foreach ($annotations as $annot) {
  524.             if ($annot instanceof Mapping\PrePersist) {
  525.                 $callbacks[] = [$method->nameEvents::prePersist];
  526.             }
  527.             if ($annot instanceof Mapping\PostPersist) {
  528.                 $callbacks[] = [$method->nameEvents::postPersist];
  529.             }
  530.             if ($annot instanceof Mapping\PreUpdate) {
  531.                 $callbacks[] = [$method->nameEvents::preUpdate];
  532.             }
  533.             if ($annot instanceof Mapping\PostUpdate) {
  534.                 $callbacks[] = [$method->nameEvents::postUpdate];
  535.             }
  536.             if ($annot instanceof Mapping\PreRemove) {
  537.                 $callbacks[] = [$method->nameEvents::preRemove];
  538.             }
  539.             if ($annot instanceof Mapping\PostRemove) {
  540.                 $callbacks[] = [$method->nameEvents::postRemove];
  541.             }
  542.             if ($annot instanceof Mapping\PostLoad) {
  543.                 $callbacks[] = [$method->nameEvents::postLoad];
  544.             }
  545.             if ($annot instanceof Mapping\PreFlush) {
  546.                 $callbacks[] = [$method->nameEvents::preFlush];
  547.             }
  548.         }
  549.         return $callbacks;
  550.     }
  551.     /**
  552.      * Parse the given JoinColumn as array
  553.      *
  554.      * @return mixed[]
  555.      *
  556.      * @psalm-return array{
  557.      *                   name: string,
  558.      *                   unique: bool,
  559.      *                   nullable: bool,
  560.      *                   onDelete: mixed,
  561.      *                   columnDefinition: string,
  562.      *                   referencedColumnName: string
  563.      *               }
  564.      */
  565.     private function joinColumnToArray(Mapping\JoinColumn $joinColumn)
  566.     {
  567.         return [
  568.             'name' => $joinColumn->name,
  569.             'unique' => $joinColumn->unique,
  570.             'nullable' => $joinColumn->nullable,
  571.             'onDelete' => $joinColumn->onDelete,
  572.             'columnDefinition' => $joinColumn->columnDefinition,
  573.             'referencedColumnName' => $joinColumn->referencedColumnName,
  574.         ];
  575.     }
  576.     /**
  577.      * Parse the given Column as array
  578.      *
  579.      * @param string $fieldName
  580.      *
  581.      * @return mixed[]
  582.      *
  583.      * @psalm-return array{
  584.      *                   fieldName: string,
  585.      *                   type: mixed,
  586.      *                   scale: int,
  587.      *                   length: int,
  588.      *                   unique: bool,
  589.      *                   nullable: bool,
  590.      *                   precision: int,
  591.      *                   options?: mixed[],
  592.      *                   columnName?: string,
  593.      *                   columnDefinition?: string
  594.      *               }
  595.      */
  596.     private function columnToArray($fieldNameMapping\Column $column)
  597.     {
  598.         $mapping = [
  599.             'fieldName' => $fieldName,
  600.             'type'      => $column->type,
  601.             'scale'     => $column->scale,
  602.             'length'    => $column->length,
  603.             'unique'    => $column->unique,
  604.             'nullable'  => $column->nullable,
  605.             'precision' => $column->precision,
  606.         ];
  607.         if ($column->options) {
  608.             $mapping['options'] = $column->options;
  609.         }
  610.         if (isset($column->name)) {
  611.             $mapping['columnName'] = $column->name;
  612.         }
  613.         if (isset($column->columnDefinition)) {
  614.             $mapping['columnDefinition'] = $column->columnDefinition;
  615.         }
  616.         return $mapping;
  617.     }
  618.     /**
  619.      * Factory method for the Annotation Driver.
  620.      *
  621.      * @param mixed[]|string $paths
  622.      *
  623.      * @return AnnotationDriver
  624.      */
  625.     public static function create($paths = [], ?AnnotationReader $reader null)
  626.     {
  627.         if ($reader === null) {
  628.             $reader = new AnnotationReader();
  629.         }
  630.         return new self($reader$paths);
  631.     }
  632. }