Generate pivot table for a belongsToMany relation.

@param string $name
@param \ProAI\Datamapper\Annotations\Annotation $annotation
@param \ProAI\Datamapper\Metadata\Definitions\Class $entityMetadata
@return \ProAI\Datamapper\Metadata\Definitions\Table
512524
protected function generateBelongsToManyPivotTable($name, Annotation $annotation, EntityDefinition &$entityMetadata)
    {
        $tableName = ($annotation->pivotTable)
            ? $annotation->pivotTable
            : $this->generatePivotTablename($entityMetadata['class'], $annotation->relatedEntity, $annotation->inverse);

        $localPivotKey = $annotation->localForeignKey ?: $this->generateKey($entityMetadata['class']);

        $relatedPivotKey = $annotation->relatedForeignKey ?: $this->generateKey($annotation->relatedEntity);

        return new TableDefinition([
            'name' => $tableName,
            'columns' => [
                $this->getModifiedPrimaryKeyColumn($entityMetadata['table'], [
                    'name' => $localPivotKey,
                    'options' => [
                        'autoIncrement' => false
                    ]
                ]),
                $this->getModifiedPrimaryKeyColumn($entityMetadata['table'], [
                    'name' => $relatedPivotKey,
                    'options' => [
                        'autoIncrement' => false
                    ]
                ]),
            ]
        ]);
    }
994142
public function get($entity, $annotation)
    {
        $properties = $this->getProperties($entity);

        $return = array();
        foreach ($properties as $reflectionProperty) {
            $propertyAnnotation = $this->reader->getPropertyAnnotation($reflectionProperty, $annotation);
            if (!is_null($propertyAnnotation) && get_class($propertyAnnotation) == $annotation) {
                $return[$reflectionProperty->name] = $reflectionProperty;
            }
        }

        return $return;
    }
324609
protected function getValidator($name, $annotation) {
        $validate = array();
        switch($name) {
            case 'NotBlank':
            case 'NotNull':
                $validate['type'] = "presence";
                break;
            case 'Email':
                $validate['type'] = "email";
                break;
            case 'Length':
                $validate['type'] = "length";
                $validate['max'] = (int)$annotation->max;
                $validate['min'] = (int)$annotation->min;
                break;
            case 'Regex':
                if ($annotation->match) {
                    $validate['type'] = "format";
                    $validate['matcher']['skipEncode'] = true;
                    $validate['matcher']['value'] = $annotation->pattern;
                }
                break;
            case 'MaxLength':
            case 'MinLength':
                $validate['type'] = "length";
                if ($name == "MaxLength") {
                    $validate['max'] = (int)$annotation->limit;
                } else {
                    $validate['min'] = (int)$annotation->limit;
                }
                break;
            case 'Choice':
                $validate['type'] = "inclusion";
                $validate['list'] = $annotation->choices;
                break;
            default:
                $validate['type'] = strtolower($name);
                $validate += get_object_vars($annotation);
                break;
        }
        return $validate;
    }
537255
private function convertReflectionPropertyToManyToManyAssociationMetadata(
        ReflectionProperty $reflectionProperty,
        array $propertyAnnotations,
        Mapping\ClassMetadata $metadata,
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
    ) : Mapping\ManyToManyAssociationMetadata {
        $className       = $metadata->getClassName();
        $fieldName       = $reflectionProperty->getName();
        $manyToManyAnnot = $propertyAnnotations[Annotation\ManyToMany::class];
        $assocMetadata   = new Mapping\ManyToManyAssociationMetadata($fieldName);
        $targetEntity    = $manyToManyAnnot->targetEntity;

        $assocMetadata->setTargetEntity($targetEntity);
        $assocMetadata->setCascade($this->getCascade($className, $fieldName, $manyToManyAnnot->cascade));
        $assocMetadata->setOrphanRemoval($manyToManyAnnot->orphanRemoval);
        $assocMetadata->setFetchMode($this->getFetchMode($className, $manyToManyAnnot->fetch));

        if (! empty($manyToManyAnnot->mappedBy)) {
            $assocMetadata->setMappedBy($manyToManyAnnot->mappedBy);
        }

        if (! empty($manyToManyAnnot->inversedBy)) {
            $assocMetadata->setInversedBy($manyToManyAnnot->inversedBy);
        }

        if (! empty($manyToManyAnnot->indexBy)) {
            $assocMetadata->setIndexedBy($manyToManyAnnot->indexBy);
        }

        // Check for JoinTable
        if (isset($propertyAnnotations[Annotation\JoinTable::class])) {
            $joinTableAnnot    = $propertyAnnotations[Annotation\JoinTable::class];
            $joinTableMetadata = $this->convertJoinTableAnnotationToJoinTableMetadata($joinTableAnnot);

            $assocMetadata->setJoinTable($joinTableMetadata);
        }

        // Check for OrderBy
        if (isset($propertyAnnotations[Annotation\OrderBy::class])) {
            $orderByAnnot = $propertyAnnotations[Annotation\OrderBy::class];

            $assocMetadata->setOrderBy($orderByAnnot->value);
        }

        // Check for Id
        if (isset($propertyAnnotations[Annotation\Id::class])) {
            throw Mapping\MappingException::illegalToManyIdentifierAssociation($className, $fieldName);
        }

        $this->attachAssociationPropertyCache($propertyAnnotations, $reflectionProperty, $assocMetadata, $metadata);

        return $assocMetadata;
    }
1358244
private function convertReflectionPropertyToOneToManyAssociationMetadata(
        ReflectionProperty $reflectionProperty,
        array $propertyAnnotations,
        Mapping\ClassMetadata $metadata,
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
    ) : Mapping\OneToManyAssociationMetadata {
        $className      = $metadata->getClassName();
        $fieldName      = $reflectionProperty->getName();
        $oneToManyAnnot = $propertyAnnotations[Annotation\OneToMany::class];
        $assocMetadata  = new Mapping\OneToManyAssociationMetadata($fieldName);
        $targetEntity   = $oneToManyAnnot->targetEntity;

        $assocMetadata->setTargetEntity($targetEntity);
        $assocMetadata->setCascade($this->getCascade($className, $fieldName, $oneToManyAnnot->cascade));
        $assocMetadata->setOrphanRemoval($oneToManyAnnot->orphanRemoval);
        $assocMetadata->setFetchMode($this->getFetchMode($className, $oneToManyAnnot->fetch));

        if (! empty($oneToManyAnnot->mappedBy)) {
            $assocMetadata->setMappedBy($oneToManyAnnot->mappedBy);
        }

        if (! empty($oneToManyAnnot->indexBy)) {
            $assocMetadata->setIndexedBy($oneToManyAnnot->indexBy);
        }

        // Check for OrderBy
        if (isset($propertyAnnotations[Annotation\OrderBy::class])) {
            $orderByAnnot = $propertyAnnotations[Annotation\OrderBy::class];

            $assocMetadata->setOrderBy($orderByAnnot->value);
        }

        // Check for Id
        if (isset($propertyAnnotations[Annotation\Id::class])) {
            throw Mapping\MappingException::illegalToManyIdentifierAssociation($className, $fieldName);
        }

        $this->attachAssociationPropertyCache($propertyAnnotations, $reflectionProperty, $assocMetadata, $metadata);

        return $assocMetadata;
    }
57132
protected function createLink(LinkAnnotation $annotation): Link
    {
        if (! preg_match(self::RESOURCE_PATTERN, $annotation->resource, $matches)) {
            throw new \LogicException(sprintf('Invalid resource definition: "%s"', $annotation->resource));
        }

        $link = new Link(
            $annotation->name,
            $matches['repository'],
            $matches['link']
        );

        $link->setParameters($annotation->parameters);
        $link->setMetadata($annotation->metadata);

        return $link;
    }
1161789
private function convertClassAnnotationsToMappedSuperClassMetadata(
        array $classAnnotations,
        ReflectionClass $reflectionClass,
        Mapping\ClassMetadata $parent
    ) {
        /** @var Annotation\MappedSuperclass $mappedSuperclassAnnot */
        $mappedSuperclassAnnot = $classAnnotations[Annotation\MappedSuperclass::class];
        $classMetadata         = new Mapping\MappedSuperClassMetadata($reflectionClass->getName(), $parent);

        if ($mappedSuperclassAnnot->repositoryClass !== null) {
            $classMetadata->setCustomRepositoryClassName($mappedSuperclassAnnot->repositoryClass);
        }

        return $classMetadata;
    }
697269
protected function parseColumn($name, Annotation $annotation, EntityDefinition &$entityMetadata, $embedded=false, $primaryKeyOnly = false)
    {
        if ($annotation->primary == $primaryKeyOnly) {
            // set column data
            if (! empty($entityMetadata['versionTable']) && $annotation->versioned) {
                $entityMetadata['versionTable']['columns'][] = $this->generateColumn($name, $annotation);
            } else {
                $entityMetadata['table']['columns'][] = $this->generateColumn($name, $annotation);
            }

            // set up version feature
            if (! empty($entityMetadata['versionTable']) && ! $annotation->versioned && $annotation->primary) {
                $this->generateVersionTable($name, $annotation, $entityMetadata);
            }
        }

        return $this->generateAttribute($name, $annotation);
    }
773366
public function generateEntityClass(EntityMetadata $meta)
    {
        $placeholders = array(
            '<namespace>',
            '<classAnnotations>',
            '<className>',
            '<implements>',
            '<traits>'
        );

        $replacements = array(
            $this->namespace,
            $this->generateClassAnnotations($meta->getTraits()),
            $meta->getClassName(),
            $this->generateImplements($meta->getInterfaces()),
            $this->generateTraitStatements($meta->getTraits())
        );

        $code = str_replace($placeholders, $replacements, self::$template);
        return str_replace('<spaces>', self::$spaces, $code);
    }
1096723
protected function evaluateOverridesAnnotations(array $classAnnotations, ORM\ClassMetadataInfo $metadata)
    {
        if (isset($classAnnotations[ORM\AssociationOverrides::class])) {
            $associationOverridesAnnotation = $classAnnotations[ORM\AssociationOverrides::class];

            foreach ($associationOverridesAnnotation->value as $associationOverride) {
                $override = [];
                $fieldName = $associationOverride->name;

                // Check for JoinColumn/JoinColumns annotations
                if ($associationOverride->joinColumns) {
                    $joinColumns = [];
                    foreach ($associationOverride->joinColumns as $joinColumn) {
                        $joinColumns[] = $this->joinColumnToArray($joinColumn);
                    }
                    $override['joinColumns'] = $joinColumns;
                }

                // Check for JoinTable annotations
                if ($associationOverride->joinTable) {
                    $joinTable = null;
                    $joinTableAnnotation = $associationOverride->joinTable;
                    $joinTable = [
                        'name' => $joinTableAnnotation->name,
                        'schema' => $joinTableAnnotation->schema
                    ];

                    foreach ($joinTableAnnotation->joinColumns as $joinColumn) {
                        $joinTable['joinColumns'][] = $this->joinColumnToArray($joinColumn);
                    }

                    foreach ($joinTableAnnotation->inverseJoinColumns as $joinColumn) {
                        $joinTable['inverseJoinColumns'][] = $this->joinColumnToArray($joinColumn);
                    }

                    $override['joinTable'] = $joinTable;
                }

                $metadata->setAssociationOverride($fieldName, $override);
            }
        }

        // Evaluate AttributeOverrides annotation
        if (isset($classAnnotations[ORM\AttributeOverrides::class])) {
            $attributeOverridesAnnotation = $classAnnotations[ORM\AttributeOverrides::class];
            foreach ($attributeOverridesAnnotation->value as $attributeOverrideAnnotation) {
                $attributeOverride = $this->addColumnToMappingArray($attributeOverrideAnnotation->column, [], $attributeOverrideAnnotation->name);
                $metadata->setAttributeOverride($attributeOverrideAnnotation->name, $attributeOverride);
            }
        }
    }
63967
protected function _createAnnotation($name, $parsedData)
    {
        $class = static::$_classMap['default'];
        if (isset(static::$_classMap[$name])) {
            $class = static::$_classMap[$name];
        }

        $classReflection = new ReflectionClass($class);
        return $classReflection->newInstanceArgs([$name, $parsedData]);
    }
97129
protected static function prepareOneToMany(&$ret, $instance, $member, $annot = null) {
		$class = get_class ( $instance );
		if (! isset ( $annot ))
			$annot = OrmUtils::getAnnotationInfoMember ( $class, "#oneToMany", $member );
		if ($annot !== false) {
			$fkAnnot = OrmUtils::getAnnotationInfoMember ( $annot ["className"], "#joinColumn", $annot ["mappedBy"] );
			if ($fkAnnot !== false) {
				$fkv = OrmUtils::getFirstKeyValue ( $instance );
				$key = $annot ["className"] . "|" . $member . "|" . $annot ["mappedBy"] . "|" . $fkAnnot ["className"];
				if (! isset ( $ret [$key] )) {
					$ret [$key] = new PendingRelationsRequest ();
				}
				$ret [$key]->addPartObject ( $instance, $fkAnnot ["name"] . "= ?", $fkv );
			}
		}
	}
1041469
public function getSchemaFromMetadata(array $metadata)
    {
        $entityMetadataSchemaConfig = $this->schemaManager->createSchemaConfig();
        $schema = new Schema([], [], $entityMetadataSchemaConfig);
        $pivotTables = [];

        foreach ($metadata as $entityMetadata) {
            $this->generateTableFromMetadata($schema, $entityMetadata['table']);
            // create version table
            if (! empty($entityMetadata['versionTable'])) {
                $this->generateTableFromMetadata($schema, $entityMetadata['versionTable']);
            }

            foreach ($entityMetadata['relations'] as $relationMetadata) {
                if (! empty($relationMetadata['pivotTable'])) {
                    // create pivot table for many to many relations
                    if (! in_array($relationMetadata['pivotTable']['name'], $pivotTables)) {
                        $this->generateTableFromMetadata($schema, $relationMetadata['pivotTable']);
                    }

                    $pivotTables[] = $relationMetadata['pivotTable']['name'];
                }
            }
        }

        return $schema;
    }
1196811
protected function callRelationship($name)
    {
        // Get the relationship arguments
        $args = $this->getRelationship($name);

        // Build the relationship
        $method = array_shift($args);
        $relationship = call_user_func_array([$this, $method], $args);

        // Check to see if this relationship has extended pivot attributes
        if ($this->hasPivotAttributes($name)) {
            // Add timestamps to relationship
            $attributes = $this->getPivotAttributes($name);
            if (in_array('timestamps', $attributes)) {
                unset($attributes[array_search('timestamps', $attributes)]);
                $relationship->withTimestamps();
            }

            // Add the pivot attributes to the relationship
            $relationship->withPivot($attributes);
        }

        return $relationship;
    }
862028
private function convertClassAnnotationsToEntityClassMetadata(
        array $classAnnotations,
        ReflectionClass $reflectionClass,
        Mapping\ClassMetadata $parent
    ) {
        /** @var Annotation\Entity $entityAnnot */
        $entityAnnot   = $classAnnotations[Annotation\Entity::class];
        $classMetadata = new Mapping\ClassMetadata($reflectionClass->getName(), $parent);

        if ($entityAnnot->repositoryClass !== null) {
            $classMetadata->setCustomRepositoryClassName($entityAnnot->repositoryClass);
        }

        if ($entityAnnot->readOnly) {
            $classMetadata->asReadOnly();
        }

        // Evaluate @Table annotation
        if (isset($classAnnotations[Annotation\Table::class])) {
            /** @var Annotation\Table $tableAnnot */
            $tableAnnot = $classAnnotations[Annotation\Table::class];
            $table      = $this->convertTableAnnotationToTableMetadata($tableAnnot);

            $classMetadata->setTable($table);
        }

        // Evaluate @ChangeTrackingPolicy annotation
        if (isset($classAnnotations[Annotation\ChangeTrackingPolicy::class])) {
            /** @var Annotation\ChangeTrackingPolicy $changeTrackingAnnot */
            $changeTrackingAnnot = $classAnnotations[Annotation\ChangeTrackingPolicy::class];

            $classMetadata->setChangeTrackingPolicy(
                constant(sprintf('%s::%s', Mapping\ChangeTrackingPolicy::class, $changeTrackingAnnot->value))
            );
        }

        // Evaluate @EntityListeners annotation
        if (isset($classAnnotations[Annotation\EntityListeners::class])) {
            /** @var Annotation\EntityListeners $entityListenersAnnot */
            $entityListenersAnnot = $classAnnotations[Annotation\EntityListeners::class];

            foreach ($entityListenersAnnot->value as $listenerClassName) {
                if (! class_exists($listenerClassName)) {
                    throw Mapping\MappingException::entityListenerClassNotFound(
                        $listenerClassName,
                        $reflectionClass->getName()
                    );
                }

                $listenerClass = new ReflectionClass($listenerClassName);

                /** @var ReflectionMethod $method */
                foreach ($listenerClass->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
                    foreach ($this->getMethodCallbacks($method) as $callback) {
                        $classMetadata->addEntityListener($callback, $listenerClassName, $method->getName());
                    }
                }
            }
        }

        // Evaluate @HasLifecycleCallbacks annotation
        if (isset($classAnnotations[Annotation\HasLifecycleCallbacks::class])) {
            /** @var ReflectionMethod $method */
            foreach ($reflectionClass->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
                foreach ($this->getMethodCallbacks($method) as $callback) {
                    $classMetadata->addLifecycleCallback($method->getName(), $callback);
                }
            }
        }

        // Evaluate @InheritanceType annotation
        if (isset($classAnnotations[Annotation\InheritanceType::class])) {
            /** @var Annotation\InheritanceType $inheritanceTypeAnnot */
            $inheritanceTypeAnnot = $classAnnotations[Annotation\InheritanceType::class];

            $classMetadata->setInheritanceType(
                constant(sprintf('%s::%s', Mapping\InheritanceType::class, $inheritanceTypeAnnot->value))
            );

            if ($classMetadata->inheritanceType !== Mapping\InheritanceType::NONE) {
                $discriminatorColumn = new Mapping\DiscriminatorColumnMetadata();

                // Evaluate @DiscriminatorColumn annotation
                if (isset($classAnnotations[Annotation\DiscriminatorColumn::class])) {
                    /** @var Annotation\DiscriminatorColumn $discriminatorColumnAnnot */
                    $discriminatorColumnAnnot = $classAnnotations[Annotation\DiscriminatorColumn::class];

                    $discriminatorColumn->setColumnName($discriminatorColumnAnnot->name);

                    if (! empty($discriminatorColumnAnnot->columnDefinition)) {
                        $discriminatorColumn->setColumnDefinition($discriminatorColumnAnnot->columnDefinition);
                    }

                    if (! empty($discriminatorColumnAnnot->type)) {
                        $discriminatorColumn->setType(Type::getType($discriminatorColumnAnnot->type));
                    }

                    if (! empty($discriminatorColumnAnnot->length)) {
                        $discriminatorColumn->setLength($discriminatorColumnAnnot->length);
                    }
                }

                if (empty($discriminatorColumn->getColumnName())) {
                    throw Mapping\MappingException::nameIsMandatoryForDiscriminatorColumns($reflectionClass->getName());
                }

                $classMetadata->setDiscriminatorColumn($discriminatorColumn);

                // Evaluate @DiscriminatorMap annotation
                if (isset($classAnnotations[Annotation\DiscriminatorMap::class])) {
                    /** @var Annotation\DiscriminatorMap $discriminatorMapAnnotation */
                    $discriminatorMapAnnotation = $classAnnotations[Annotation\DiscriminatorMap::class];
                    $discriminatorMap           = $discriminatorMapAnnotation->value;

                    $classMetadata->setDiscriminatorMap($discriminatorMap);
                }
            }
        }

        return $classMetadata;
    }
882918
private function convertJoinTableAnnotationToJoinTableMetadata(
        Annotation\JoinTable $joinTableAnnot
    ) : Mapping\JoinTableMetadata {
        $joinTable = new Mapping\JoinTableMetadata();

        if (! empty($joinTableAnnot->name)) {
            $joinTable->setName($joinTableAnnot->name);
        }

        if (! empty($joinTableAnnot->schema)) {
            $joinTable->setSchema($joinTableAnnot->schema);
        }

        foreach ($joinTableAnnot->joinColumns as $joinColumnAnnot) {
            $joinColumn = $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot);

            $joinTable->addJoinColumn($joinColumn);
        }

        foreach ($joinTableAnnot->inverseJoinColumns as $joinColumnAnnot) {
            $joinColumn = $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot);

            $joinTable->addInverseJoinColumn($joinColumn);
        }

        return $joinTable;
    }
301388
protected function evaluatePropertyAnnotations(ORM\ClassMetadataInfo $metadata)
    {
        $className = $metadata->name;

        $class = $metadata->getReflectionClass();
        $classSchema = $this->getClassSchema($className);

        foreach ($class->getProperties() as $property) {
            if (!$classSchema->hasProperty($property->getName())
                    || $classSchema->isPropertyTransient($property->getName())
                    || $metadata->isMappedSuperclass && !$property->isPrivate()
                    || $metadata->isInheritedField($property->getName())
                    || $metadata->isInheritedAssociation($property->getName())
                    || $metadata->isInheritedEmbeddedClass($property->getName())) {
                continue;
            }

            $propertyMetaData = $classSchema->getProperty($property->getName());

            $mapping = [];
            $mapping['fieldName'] = $property->getName();
            $mapping['columnName'] = strtolower($property->getName());
            $mapping['targetEntity'] = $propertyMetaData['type'];

            $joinColumns = $this->evaluateJoinColumnAnnotations($property);

            // Field can only be annotated with one of:
            // @OneToOne, @OneToMany, @ManyToOne, @ManyToMany, @Column (optional)
            if ($oneToOneAnnotation = $this->reader->getPropertyAnnotation($property, ORM\OneToOne::class)) {
                if ($this->reader->getPropertyAnnotation($property, ORM\Id::class) !== null) {
                    $mapping['id'] = true;
                }
                if ($oneToOneAnnotation->targetEntity !== null) {
                    $mapping['targetEntity'] = $oneToOneAnnotation->targetEntity;
                }
                if ($oneToOneAnnotation->inversedBy !== null || $oneToOneAnnotation->mappedBy === null) {
                    $mapping['joinColumns'] = $this->buildJoinColumnsIfNeeded($joinColumns, $mapping, $property);
                }
                $mapping['mappedBy'] = $oneToOneAnnotation->mappedBy;
                $mapping['inversedBy'] = $oneToOneAnnotation->inversedBy;
                if ($oneToOneAnnotation->cascade !== null) {
                    $mapping['cascade'] = $oneToOneAnnotation->cascade;
                } elseif ($this->isValueObject($mapping['targetEntity'], $className)) {
                    $mapping['cascade'] = ['persist'];
                } elseif ($this->isAggregateRoot($mapping['targetEntity'], $className) === false) {
                    $mapping['cascade'] = ['all'];
                }
                // We need to apply our value for non-aggregate roots first, because Doctrine sets a default value for orphanRemoval (see #1127)
                if ($this->isAggregateRoot($mapping['targetEntity'], $className) === false &&
                    $this->isValueObject($mapping['targetEntity'], $className) === false) {
                    $mapping['orphanRemoval'] = true;
                } elseif ($oneToOneAnnotation->orphanRemoval !== null) {
                    $mapping['orphanRemoval'] = $oneToOneAnnotation->orphanRemoval;
                }
                $mapping['fetch'] = $this->getFetchMode($className, $oneToOneAnnotation->fetch);
                $metadata->mapOneToOne($mapping);
            } elseif ($oneToManyAnnotation = $this->reader->getPropertyAnnotation($property, ORM\OneToMany::class)) {
                $mapping['mappedBy'] = $oneToManyAnnotation->mappedBy;
                if ($oneToManyAnnotation->targetEntity !== null) {
                    $mapping['targetEntity'] = $oneToManyAnnotation->targetEntity;
                } elseif (isset($propertyMetaData['elementType'])) {
                    $mapping['targetEntity'] = $propertyMetaData['elementType'];
                }
                if ($oneToManyAnnotation->cascade !== null) {
                    $mapping['cascade'] = $oneToManyAnnotation->cascade;
                } elseif ($this->isValueObject($mapping['targetEntity'], $className)) {
                    $mapping['cascade'] = ['persist'];
                } elseif ($this->isAggregateRoot($mapping['targetEntity'], $className) === false) {
                    $mapping['cascade'] = ['all'];
                }
                $mapping['indexBy'] = $oneToManyAnnotation->indexBy;
                // We need to apply our value for non-aggregate roots first, because Doctrine sets a default value for orphanRemoval (see #1127)
                if ($this->isAggregateRoot($mapping['targetEntity'], $className) === false &&
                    $this->isValueObject($mapping['targetEntity'], $className) === false) {
                    $mapping['orphanRemoval'] = true;
                } elseif ($oneToManyAnnotation->orphanRemoval !== null) {
                    $mapping['orphanRemoval'] = $oneToManyAnnotation->orphanRemoval;
                }
                $mapping['fetch'] = $this->getFetchMode($className, $oneToManyAnnotation->fetch);

                if ($orderByAnnotation = $this->reader->getPropertyAnnotation($property, ORM\OrderBy::class)) {
                    $mapping['orderBy'] = $orderByAnnotation->value;
                }

                $metadata->mapOneToMany($mapping);
            } elseif ($manyToOneAnnotation = $this->reader->getPropertyAnnotation($property, ORM\ManyToOne::class)) {
                if ($this->reader->getPropertyAnnotation($property, ORM\Id::class) !== null) {
                    $mapping['id'] = true;
                }
                if ($manyToOneAnnotation->targetEntity !== null) {
                    $mapping['targetEntity'] = $manyToOneAnnotation->targetEntity;
                }

                $mapping['joinColumns'] = $this->buildJoinColumnsIfNeeded($joinColumns, $mapping, $property);
                if ($manyToOneAnnotation->cascade !== null) {
                    $mapping['cascade'] = $manyToOneAnnotation->cascade;
                } elseif ($this->isValueObject($mapping['targetEntity'], $className)) {
                    $mapping['cascade'] = ['persist'];
                } elseif ($this->isAggregateRoot($mapping['targetEntity'], $className) === false) {
                    $mapping['cascade'] = ['all'];
                }
                $mapping['inversedBy'] = $manyToOneAnnotation->inversedBy;
                $mapping['fetch'] = $this->getFetchMode($className, $manyToOneAnnotation->fetch);
                $metadata->mapManyToOne($mapping);
            } elseif ($manyToManyAnnotation = $this->reader->getPropertyAnnotation($property, ORM\ManyToMany::class)) {
                if ($manyToManyAnnotation->targetEntity !== null) {
                    $mapping['targetEntity'] = $manyToManyAnnotation->targetEntity;
                } elseif (isset($propertyMetaData['elementType'])) {
                    $mapping['targetEntity'] = $propertyMetaData['elementType'];
                }
                /** @var ORM\JoinTable $joinTableAnnotation */
                if ($joinTableAnnotation = $this->reader->getPropertyAnnotation($property, ORM\JoinTable::class)) {
                    $joinTable = $this->evaluateJoinTableAnnotation($joinTableAnnotation, $property, $className, $mapping);
                } else {
                    $joinColumns = [
                        [
                            'name' => null,
                            'referencedColumnName' => null,
                        ]
                    ];

                    $joinTable = [
                        'name' => $this->inferJoinTableNameFromClassAndPropertyName($className, $property->getName()),
                        'joinColumns' => $this->buildJoinColumnsIfNeeded($joinColumns, $mapping, $property, self::MAPPING_MM_REGULAR),
                        'inverseJoinColumns' => $this->buildJoinColumnsIfNeeded($joinColumns, $mapping, $property)
                    ];
                }

                $mapping['joinTable'] = $joinTable;
                $mapping['mappedBy'] = $manyToManyAnnotation->mappedBy;
                $mapping['inversedBy'] = $manyToManyAnnotation->inversedBy;
                if ($manyToManyAnnotation->cascade !== null) {
                    $mapping['cascade'] = $manyToManyAnnotation->cascade;
                } elseif ($this->isValueObject($mapping['targetEntity'], $className)) {
                    $mapping['cascade'] = ['persist'];
                } elseif ($this->isAggregateRoot($mapping['targetEntity'], $className) === false) {
                    $mapping['cascade'] = ['all'];
                }
                $mapping['indexBy'] = $manyToManyAnnotation->indexBy;
                $mapping['orphanRemoval'] = $manyToManyAnnotation->orphanRemoval;
                $mapping['fetch'] = $this->getFetchMode($className, $manyToManyAnnotation->fetch);

                if ($orderByAnnotation = $this->reader->getPropertyAnnotation($property, ORM\OrderBy::class)) {
                    $mapping['orderBy'] = $orderByAnnotation->value;
                }

                $metadata->mapManyToMany($mapping);
            } elseif ($embeddedAnnotation = $this->reader->getPropertyAnnotation($property, ORM\Embedded::class)) {
                if ($embeddedAnnotation->class !== null) {
                    $mapping['class'] = $embeddedAnnotation->class;
                } else {
                    // This will not happen currently, because "class" argument is required. It would be nice if that could be changed though.
                    $mapping['class'] = $mapping['targetEntity'];
                }
                $mapping['columnPrefix'] = $embeddedAnnotation->columnPrefix;
                $metadata->mapEmbedded($mapping);
            } else {
                $mapping['nullable'] = false;

                /** @var ORM\Column $columnAnnotation */
                if ($columnAnnotation = $this->reader->getPropertyAnnotation($property, ORM\Column::class)) {
                    $mapping = $this->addColumnToMappingArray($columnAnnotation, $mapping);
                }

                if (!isset($mapping['type'])) {
                    switch ($propertyMetaData['type']) {
                        case 'DateTime':
                            $mapping['type'] = 'datetime';
                            break;
                        case 'DateTimeImmutable':
                            $mapping['type'] = 'datetime_immutable';
                            break;
                        case 'string':
                        case 'integer':
                        case 'boolean':
                        case 'float':
                        case 'array':
                            $mapping['type'] = $propertyMetaData['type'];
                            break;
                        default:
                            if (strpos($propertyMetaData['type'], '\\') !== false) {
                                if ($this->reflectionService->isClassAnnotatedWith($propertyMetaData['type'], Flow\ValueObject::class)) {
                                    $valueObjectAnnotation = $this->reflectionService->getClassAnnotation($propertyMetaData['type'], Flow\ValueObject::class);
                                    if ($valueObjectAnnotation->embedded === true) {
                                        $mapping['class'] = $propertyMetaData['type'];
                                        $mapping['columnPrefix'] = $mapping['columnName'];
                                        $metadata->mapEmbedded($mapping);
                                        // Leave switch and continue with next property
                                        continue 2;
                                    }
                                    $mapping['type'] = 'object';
                                } elseif (class_exists($propertyMetaData['type'])) {
                                    throw ORM\MappingException::missingRequiredOption($property->getName(), 'OneToOne', sprintf('The property "%s" in class "%s" has a non standard data type and doesn\'t define the type of the relation. You have to use one of these annotations: @OneToOne, @OneToMany, @ManyToOne, @ManyToMany', $property->getName(), $className));
                                }
                            } else {
                                throw ORM\MappingException::propertyTypeIsRequired($className, $property->getName());
                            }
                    }
                }

                if ($this->reader->getPropertyAnnotation($property, ORM\Id::class) !== null) {
                    $mapping['id'] = true;
                }

                if ($generatedValueAnnotation = $this->reader->getPropertyAnnotation($property, ORM\GeneratedValue::class)) {
                    $metadata->setIdGeneratorType(constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_' . strtoupper($generatedValueAnnotation->strategy)));
                }

                if ($this->reflectionService->isPropertyAnnotatedWith($className, $property->getName(), ORM\Version::class)) {
                    $metadata->setVersionMapping($mapping);
                }

                $metadata->mapField($mapping);

                // Check for SequenceGenerator/TableGenerator definition
                if ($seqGeneratorAnnotation = $this->reader->getPropertyAnnotation($property, ORM\SequenceGenerator::class)) {
                    $metadata->setSequenceGeneratorDefinition([
                        'sequenceName' => $seqGeneratorAnnotation->sequenceName,
                        'allocationSize' => $seqGeneratorAnnotation->allocationSize,
                        'initialValue' => $seqGeneratorAnnotation->initialValue
                    ]);
                } elseif ($this->reader->getPropertyAnnotation($property, ORM\TableGenerator::class) !== null) {
                    throw ORM\MappingException::tableIdGeneratorNotImplemented($className);
                } elseif ($customGeneratorAnnotation = $this->reader->getPropertyAnnotation($property, ORM\CustomIdGenerator::class)) {
                    $metadata->setCustomGeneratorDefinition([
                        'class' => $customGeneratorAnnotation->class
                    ]);
                }
            }

            // Evaluate @Cache annotation
            if (($cacheAnnotation = $this->reader->getPropertyAnnotation($property, ORM\Cache::class)) !== null) {
                $metadata->enableAssociationCache($mapping['fieldName'], [
                    'usage'         => constant('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' . $cacheAnnotation->usage),
                    'region'        => $cacheAnnotation->region,
                ]);
            }
        }
    }