Doctrine needs to cross reference fks.

{@inheritdoc}
778485
public function setFormattedImages(FormattedImageCollection $formattedImages)
    {
        return parent::setFormattedImages($formattedImages->map(function (FormattedImage $formattedImage) {
            return $formattedImage->setImage($this);
        }));
    }
325904
public function finalize(string $tableName = null): FinalizedMapperDefinition
    {
        $this->verifyDefinedClass();

        $tableName = $tableName ?: $this->tableName;

        $allMappedProperties = $this->getAllMappedProperties();

        foreach ($this->class->getProperties() as $property) {
            $propertyName = $property->getName();

            if ($this->verifyAllPropertiesMapped && !isset($allMappedProperties[$propertyName])) {
                throw IncompleteMapperDefinitionException::format(
                    'Invalid mapper definition for %s: unmapped property \'%s\' of type %s, call $map->ignoreUnmappedProperties() to ignore this warning',
                    $this->class->getClassName(),
                    $propertyName,
                    $property->getType()->asTypeString()
                );
            }
        }

        $table = new Table(
            $this->orm->getNamespace() . $tableName,
            $this->columns,
            $this->indexes,
            $this->foreignKeys
        );

        $relationsFactory = function (Table $table, IObjectMapper $parentMapper) {
            $objectType = $parentMapper->getObjectType();
            $tableName  = $parentMapper->getDefinition()->getTable()->getName();

            $relations = [];
            foreach ($this->relationFactories as $factory) {
                $relationId  = implode(':', [$objectType, $tableName, $this->getRootDefinition()->relationCount++]);
                $relations[] = $factory($relationId, $table, $parentMapper);
            }

            return $relations;
        };

        $foreignKeysFactory = function (Table $table) {
            $foreignKeys = [];

            foreach ($this->foreignKeyFactories as $factory) {
                /** @var ForeignKey $foreignKey */
                $foreignKey    = $factory($table);
                $foreignKeys[] = $foreignKey->withNamePrefixedBy($this->orm->getNamespace());
            }

            return $foreignKeys;
        };

        $persistHooks = [];
        foreach ($this->persistHookFactories as $uniqueKey => $factory) {
            $persistHooks[] = $factory($table, $uniqueKey, $this->propertyColumnMap, $this->class->getClassName());
        }

        $subClassMappings = [];
        foreach ($this->subClassMappingFactories as $factory) {
            $subClassMappings[] = $factory($table);
        }

        return new FinalizedMapperDefinition(
            $this->orm,
            $this->class,
            $table,
            $this->propertyColumnMap,
            $this->columnGetterMap,
            $this->columnSetterMap,
            $this->phpToDbPropertyConverterMap,
            $this->dbToPhpPropertyConverterMap,
            $this->methodColumnMap,
            $this->customLoadMappingCallbacks,
            $this->lockingStrategies,
            $persistHooks,
            $subClassMappings,
            $relationsFactory,
            $foreignKeysFactory
        );
    }
763045
public function resolveReferences(ReferenceContext $context = null)
    {
        foreach ($this->_properties as $property => $value) {
            if ($value instanceof Reference) {
                $this->_properties[$property] = $value->resolve($context);
            } elseif ($value instanceof SpecObjectInterface) {
                $value->resolveReferences($context);
            } elseif (is_array($value)) {
                foreach ($value as $k => $item) {
                    if ($item instanceof Reference) {
                        $this->_properties[$property][$k] = $item->resolve($context);
                    } elseif ($item instanceof SpecObjectInterface) {
                        $item->resolveReferences($context);
                    }
                }
            }
        }
    }
144259
private void setUpSimpleRelation(Entity entity, ForeignKey fk) {
        Relation forwardRelation = buildSimpleRelation(entity, fk);
        entity.addRelation(forwardRelation);

        if (shouldCreateInverse(forwardRelation)) {
            Relation reverseRelation = forwardRelation.createInverse();
            forwardRelation.getToEntity().addRelation(reverseRelation);
        }
    }
157438
public function hasOne($entity, string $related, string $foreignKey = null, string $localKey = null): HasOne
    {
        $foreignKey = $foreignKey ?: $this->getForeignKey();

        $relatedMapper = Manager::getInstance()->mapper($related);

        $relatedMap = $relatedMapper->getEntityMap();

        $localKey = $localKey ?: $this->getKeyName();

        // Add the relation to the definition in map
        list(, $caller) = debug_backtrace(false);
        $relation = $caller['function'];
        $this->relatedClasses[$relation] = $related;

        $this->addSingleRelation($relation);
        $this->addForeignRelation($relation);
        $this->addNonProxyRelation($relation);

        // This relationship will always be eager loaded, as proxying it would
        // mean having an object that doesn't actually exists.
        if (!in_array($relation, $this->with)) {
            $this->with[] = $relation;
        }

        return new HasOne($relatedMapper, $entity, /*$relatedMap->getTable().'.'*/$foreignKey, $localKey);
    }
863251
function via( $key ) {

        if ( !$this->parent_ ) throw new \LogicException( 'Cannot set reference key on basic Result' );

        $clone = clone $this;

        if ( $clone->single ) {

            $clone->parentKey = $key;

        } else {

            $clone->key = $key;

        }

        return $clone;

    }
138503
public function getForeignKeyDDL(ForeignKey $fk)
    {
        if ($fk->isSkipSql()) {
            return;
        }
        $pattern = "CONSTRAINT %s
    FOREIGN KEY (%s)
    REFERENCES %s (%s)";
        $script = sprintf($pattern,
            $this->quoteIdentifier($fk->getName()),
            $this->getColumnListDDL($fk->getLocalColumns()),
            $this->quoteIdentifier($fk->getForeignTableName()),
            $this->getColumnListDDL($fk->getForeignColumns())
        );
        if ($fk->hasOnUpdate()) {
            $script .= "
    ON UPDATE " . $fk->getOnUpdate();
        }
        if ($fk->hasOnDelete()) {
            $script .= "
    ON DELETE " . $fk->getOnDelete();
        }

        return $script;
    }
24880
public function addReference($ref1, $ref2 = null)
    {
        if (is_array($ref1)) {
            $this->localColumns[] = isset($ref1['local']) ? $ref1['local'] : null;
            $this->foreignColumns[] = isset($ref1['foreign']) ? $ref1['foreign'] : null;
            $this->localValues[] = isset($ref1['value']) ? $ref1['value'] : null;

            return;
        }

        if (is_string($ref1)) {
            $this->localColumns[] = $ref1;
            $this->foreignColumns[] = is_string($ref2) ? $ref2 : null;
            $this->localValues[] = null;

            return;
        }

        $local = null;
        $foreign = null;
        if ($ref1 instanceof Column) {
            $local = $ref1->getName();
            $this->localColumns[] = $local;
        } else {
            $this->localValues[] = $local;
        }

        if ($ref2 instanceof Column) {
            $foreign = $ref2->getName();
            $this->foreignColumns[] = $foreign;
            $this->localValues[] = null;
        } else if ($ref1 instanceof Column) {
            $this->foreignColumns[] = null;
            $this->localValues[] = $ref2;
        }
    }
1167400
private function detectOneToOne($fk, $primary)
    {
        if (count($primary) === 1) {
            foreach ($primary as $prim) {
                if ($prim === $fk['field']) {
                    return true;
                }
            }
        }

        return false;
    }
16207
public function loadCollection(PersistentCollectionInterface $collection) : void
    {
        $mapping = $collection->getMapping();
        switch ($mapping['association']) {
            case ClassMetadata::EMBED_MANY:
                $this->loadEmbedManyCollection($collection);
                break;

            case ClassMetadata::REFERENCE_MANY:
                if (isset($mapping['repositoryMethod']) && $mapping['repositoryMethod']) {
                    $this->loadReferenceManyWithRepositoryMethod($collection);
                } else {
                    if ($mapping['isOwningSide']) {
                        $this->loadReferenceManyCollectionOwningSide($collection);
                    } else {
                        $this->loadReferenceManyCollectionInverseSide($collection);
                    }
                }
                break;
        }
    }
1274230
private function cascadeRemove($entity, array &$visited)
    {
        $class = $this->em->getClassMetadata(get_class($entity));

        $associationMappings = array_filter(
            $class->associationMappings,
            function ($assoc) { return $assoc['isCascadeRemove']; }
        );

        $entitiesToCascade = array();

        foreach ($associationMappings as $assoc) {
            if ($entity instanceof Proxy && !$entity->__isInitialized__) {
                $entity->__load();
            }

            $relatedEntities = $class->reflFields[$assoc['fieldName']]->getValue($entity);

            switch (true) {
                case ($relatedEntities instanceof Collection):
                case (is_array($relatedEntities)):
                    // If its a PersistentCollection initialization is intended! No unwrap!
                    foreach ($relatedEntities as $relatedEntity) {
                        $entitiesToCascade[] = $relatedEntity;
                    }
                    break;

                case ($relatedEntities !== null):
                    $entitiesToCascade[] = $relatedEntities;
                    break;

                default:
                    // Do nothing
            }
        }

        foreach ($entitiesToCascade as $relatedEntity) {
            $this->doRemove($relatedEntity, $visited);
        }
    }
1084100
public function isOwningSide($relation, &$mappedRelation)
    {
        $mappedRelation = $relation['reference']->getOwningTable()->getRelationToTable($relation['refTable']->getRawTableName());

        // user can hint which side is the owning side (set d:owningSide on the foreign key)
        if ($relation['reference']->parseComment('owningSide') === 'true') {
            return true;
        }
        if ($mappedRelation->parseComment('owningSide') === 'true') {
            return false;
        }

        // if no owning side is defined, use one side randomly as owning side (the one where the column id is lower)
        return $relation['reference']->getLocal()->getId() < $mappedRelation->getLocal()->getId();
    }
327837
private function cascadePersist($entity, array &$visited)
    {
        $class = $this->em->getClassMetadata(get_class($entity));

        if ($entity instanceof GhostObjectInterface && ! $entity->isProxyInitialized()) {
            // nothing to do - proxy is not initialized, therefore we don't do anything with it
            return;
        }

        foreach ($class->getDeclaredPropertiesIterator() as $association) {
            if (! ($association instanceof AssociationMetadata && in_array('persist', $association->getCascade(), true))) {
                continue;
            }

            /** @var AssociationMetadata $association */
            $relatedEntities = $association->getValue($entity);
            $targetEntity    = $association->getTargetEntity();

            switch (true) {
                case $relatedEntities instanceof PersistentCollection:
                    // Unwrap so that foreach() does not initialize
                    $relatedEntities = $relatedEntities->unwrap();
                    // break; is commented intentionally!

                case $relatedEntities instanceof Collection:
                case is_array($relatedEntities):
                    if (! ($association instanceof ToManyAssociationMetadata)) {
                        throw ORMInvalidArgumentException::invalidAssociation(
                            $this->em->getClassMetadata($targetEntity),
                            $association,
                            $relatedEntities
                        );
                    }

                    foreach ($relatedEntities as $relatedEntity) {
                        $this->doPersist($relatedEntity, $visited);
                    }

                    break;

                case $relatedEntities !== null:
                    if (! $relatedEntities instanceof $targetEntity) {
                        throw ORMInvalidArgumentException::invalidAssociation(
                            $this->em->getClassMetadata($targetEntity),
                            $association,
                            $relatedEntities
                        );
                    }

                    $this->doPersist($relatedEntities, $visited);
                    break;

                default:
                    // Do nothing
            }
        }
    }
203620
private void setUpCompositeRelation2(Entity entity, ForeignKey fk) {
        Relation forwardRelation = buildCompositeRelation2(entity, fk);
        entity.addRelation(forwardRelation);
        if (shouldCreateInverse(forwardRelation)) {
            Relation reverseRelation = forwardRelation.createInverse();
            forwardRelation.getToEntity().addRelation(reverseRelation);
        }
    }
672645
public function autodiscover(): void
    {
        $entityMappings = [];
        foreach ($this->fileSystem->getEntityDirectories() as $entityDirectory) {
            $namespace = $this->namespaceDetector->detectFromDirectory($entityDirectory);
            if (! $namespace) {
                continue;
            }

            $entityMappings[] = [
                'name' => $namespace, // required name
                'prefix' => $namespace,
                'type' => 'annotation',
                'dir' => $entityDirectory->getRealPath(),
                'is_bundle' => false, // performance
            ];
        }

        $xmlNamespaces = [];

        $directoryByNamespace = $this->resolveDirectoryByNamespace($this->fileSystem->getEntityXmlFiles());
        foreach ($directoryByNamespace as $namespace => $directory) {
            if (in_array($namespace, $xmlNamespaces, true)) {
                continue;
            }

            $xmlNamespaces[] = $namespace;

            $entityMappings[] = [
                'name' => $namespace, // required name
                'prefix' => $namespace,
                'type' => 'xml',
                'dir' => $directory,
                'is_bundle' => false,
            ];
        }

        if (! count($entityMappings)) {
            return;
        }

        // @see https://symfony.com/doc/current/reference/configuration/doctrine.html#mapping-entities-outside-of-a-bundle
        $this->containerBuilder->prependExtensionConfig('doctrine', [
            'orm' => ['mappings' => $entityMappings],
        ]);
    }
332054
public function getCrossFks()
    {
        $crossFks = [];
        foreach ($this->referrers as $refFK) {
            if ($refFK->getTable()->isCrossRef()) {
                $crossFK = new CrossForeignKeys($refFK, $this);
                foreach ($refFK->getOtherFks() as $fk) {
                    if ($fk->isAtLeastOneLocalPrimaryKeyIsRequired() &&
                        $crossFK->isAtLeastOneLocalPrimaryKeyNotCovered($fk)) {
                        $crossFK->addCrossForeignKey($fk);
                    }
                }
                if ($crossFK->hasCrossForeignKeys()) {
                    $crossFks[] = $crossFK;
                }
            }
        }

        return $crossFks;
    }
651774
public function fk($name, $field, $table, $fkField, $delete = 'NO ACTION', $update = 'NO ACTION') {
        $this->foreignKey[$name] = [$field, $table, $fkField, $delete, $update];
        return $this;
    }