Doctrine needs to cross reference fks.

{@inheritdoc}
768034
public function setComponents(ComponentCollection $components)
    {
        return parent::setComponents($components->map(function (Component $component) {
            return $component->setZone($this);
        }));
    }
392750
private function resolveBelongsTo($definition)
    {
        foreach ($definition->relations['belongsTo'] as $relation) {
            $counterMeta = $this->getMeta($relation->entity);

            if (empty($counterMeta)) {
                // Counter entity is unknown, ignore it
                continue;
            }

            if ($counterMeta->role == 'lookup') {
                continue;
            }

            $counterRelations = $counterMeta->relations;

            foreach (array_merge($counterRelations['hasOne'], $counterRelations['hasMany']) as $hasMany) {
                if ($hasMany->entity == $definition->name && $hasMany->reference == $relation->reference) {
                    break 2;
                }
            }

            // No existing counter-relation found; create it.
            $hasMany = new HasMany(
                [
                    'name'      => $this->normalise($this->inflector->toPlural($definition->name)),
                    'entity'    => $definition->name,
                    'reference' => $relation->reference,
                ]
            );

            $this->entityDefinitions[$counterMeta->name]->relations['hasMany'][$hasMany->name] = $hasMany;
        }
    }
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;
    }
370790
public function columns($columnNames) : ForeignKeyReferencedColumnsDefiner
    {
        return new ForeignKeyReferencedColumnsDefiner(
                $this->callback,
                is_array($columnNames) ? $columnNames : [$columnNames]
        );
    }
567713
public function insert() {
        $sql = new Sql();
        $queryFields = '';
        $queryValues = '';
        $manyToMany = null;
        $transaction = Database::instance()->db()->inTransaction();

        if (!$transaction) {
            Database::instance()->db()->beginTransaction();
        }

        /** @var $fieldsInsertOneToMany \Gcs\Framework\Core\Orm\Entity\Entity[] */
        $fieldsInsertOneToMany = [];
        /** @var $fieldsUpdateOneToMany \Gcs\Framework\Core\Orm\Entity\Entity[] */
        $fieldsUpdateOneToMany = [];

        /** @var $fieldsInsertOneToMany \Gcs\Framework\Core\Orm\Entity\Entity[] */
        $fieldsInsertManyToMany = [];
        /** @var $fieldsUpdateOneToMany \Gcs\Framework\Core\Orm\Entity\Entity[] */
        $fieldsUpdateManyToMany = [];

        /** @var $field \Gcs\Framework\Core\Orm\Entity\Field */
        foreach ($this->_fields as $field) {
            if ($field->primary == false) {
                if ($field->foreign != null) {
                    switch ($field->foreign->type()) {
                        case ForeignKey::ONE_TO_ONE :
                            if (gettype($field->value) != 'object') {
                                if ($field->foreign->belong() == ForeignKey::COMPOSITION) {
                                    throw new MissingEntityException('The field "' . $field->name . '" in "' . $this->_name . '" must be an Entity');
                                }
                            }
                            if (!preg_match('#Orm\\\Entity\\\#isU', get_class($field->value))) {
                                throw new MissingEntityException('The foreign key "' . $field->name . '" in "' . $this->_name . '" must be an Entity object');
                            }

                            if ($field->value == null) {
                                throw new MissingEntityException('The field "' . $field->name . '" in "' . $this->_name . '" can\'t be null');
                            }
                            else {
                                $field->value->insert();
                                $queryFields .= $field->name . ', ';
                                $queryValues .= ':' . $field->name . ', ';
                                $sql->vars($field->name, $field->value->get($field->foreign->referenceField()));
                            }
                            break;

                        case ForeignKey::MANY_TO_ONE :
                            if (gettype($field->value) != 'object') {
                                if ($field->foreign->belong() == ForeignKey::COMPOSITION) {
                                    throw new MissingEntityException('The field "' . $field->name . '" in "' . $this->_name . '" must be an Entity');
                                }
                            }
                            if (!preg_match('#Orm\\\Entity\\\#isU', get_class($field->value))) {
                                //throw new MissingEntityException('The foreign key "'.$field->name.'" in "'.$this->_name.'" must be an Entity object');
                            }

                            if ($field->value == null) {
                                if ($field->beNull == false) {
                                    throw new MissingEntityException('The field "' . $field->name . '" in "' . $this->_name . '" can\'t be null');
                                }
                                else {
                                    $sql->vars($field->name, null);
                                    $queryFields .= $field->name . ', ';
                                    $queryValues .= ':' . $field->name . ', ';
                                }
                            }
                            else {
                                if ($field->value->get($field->value->primary()) == null) {
                                    $field->value->insert();
                                }

                                $sql->vars($field->name, $field->value->get($field->foreign->referenceField()));
                                $queryFields .= $field->name . ', ';
                                $queryValues .= ':' . $field->name . ', ';
                            }
                            break;

                        case ForeignKey::ONE_TO_MANY :
                            /** If we insert a new line and the are already sub-objects, we have to insert or update them */
                            if ($field->value != null) {
                                if (gettype($field->value) != 'object') {
                                    if ($field->foreign->belong() == ForeignKey::COMPOSITION) {
                                        throw new MissingEntityException('The field "' . $field->name . '" in "' . $this->_name . '" must be a Collection');
                                    }
                                }
                                if (get_class($field->value) != 'System\Collection\Collection') {
                                    throw new MissingEntityException('The field "' . $field->name . '" in "' . $this->_name . '" must be a Collection');
                                }

                                /** @var $entity \Gcs\Framework\Core\Orm\Entity\Entity */
                                foreach ($field->value as $entity) {
                                    if ($entity != null) {
                                        /** the field is not in database yet, so after the insert of the Entity, we will do an insert for it */
                                        if ($entity->get($entity->primary()) == null) {
                                            array_push($fieldsInsertOneToMany, $entity);
                                        }
                                        /** the field is already in database, so after the insert of the Entity, we will do an update for it */
                                        else if ($entity->get($entity->primary()) != null) {
                                            array_push($fieldsUpdateOneToMany, $entity);
                                        }
                                    }
                                    else {
                                        if ($field->foreign->belong() == ForeignKey::COMPOSITION) {
                                            throw new MissingEntityException('The Collection field "' . $field->name . '" in "' . $this->_name . '" can\'t have a null item because the relation is a composition');
                                        }
                                    }
                                }
                            }
                            else if ($field->beNull == false) {
                                throw new MissingEntityException('The field "' . $field->name . '" in "' . $this->_name . '" can\'t be null');
                            }
                            break;

                        case ForeignKey::MANY_TO_MANY :
                            /** If we insert a new line and the are already sub-objects, we have to insert or update them */
                            if ($field->value != null) {
                                if (gettype($field->value) != 'object') {
                                    throw new MissingEntityException('The field "' . $field->name . '" in "' . $this->_name . '" must be a Collection');
                                }
                                if (get_class($field->value) != 'System\Collection\Collection') {
                                    throw new MissingEntityException('The field "' . $field->name . '" in "' . $this->_name . '" must be a Collection');
                                }

                                $manyToMany = $field;

                                /** @var $entity \Gcs\Framework\Core\Orm\Entity\Entity */
                                foreach ($field->value as $entity) {
                                    if ($entity != null) {
                                        /** the field is not in database yet, so after the insert of the Entity, we will do an insert for it */
                                        if ($entity->get($entity->primary()) == null) {
                                            array_push($fieldsInsertManyToMany, $entity);
                                        }
                                        else {
                                            array_push($fieldsUpdateManyToMany, $entity);
                                        }
                                    }
                                    else {
                                        throw new MissingEntityException('The Entity "' . get_class($entity) . '" in "' . $this->_name . '" can\'t be null');
                                    }
                                }
                            }
                            else if ($field->beNull == false) {
                                throw new MissingEntityException('The field "' . $field->name . '" in "' . $this->_name . '" can\'t be null');
                            }
                            break;
                    }
                }
                else {
                    if ($field->value == null && $field->beNull == false && $field->default == '') {
                        throw new MissingEntityException('The field "' . $field->name . '" in "' . $this->_name . '" can\'t be null');
                    }

                    if ($field->value == null) {
                        $field->value = $field->default;
                    }

                    if (gettype($field->value) != 'object') {
                        if (in_array($field->type, [Field::INCREMENT, Field::INT, Field::FLOAT])) {
                            $sql->vars($field->name, [$field->value, Sql::PARAM_INT]);
                        }
                        else if (in_array($field->type, [Field::CHAR, Field::TEXT, Field::STRING, Field::DATE, Field::DATETIME, Field::TIMESTAMP])) {
                            $sql->vars($field->name, [$field->value, Sql::PARAM_STR]);
                        }
                        else if (in_array($field->type, [Field::BOOL])) {
                            $sql->vars($field->name, [$field->value, Sql::PARAM_BOOL]);
                        }
                        else {
                            $sql->vars($field->name, $field->value);
                        }
                    }
                    elseif (in_array($field->type, [Field::DATETIME, Field::TIMESTAMP])) {
                        $sql->vars($field->name, [$field->value->format("Y-m-d H:i:s"), Sql::PARAM_STR]);
                    }
                    elseif (in_array($field->type, [Field::DATE])) {
                        $sql->vars($field->name, [$field->value->format("Y-m-d"), Sql::PARAM_STR]);
                    }
                    else if (in_array($field->type, [Field::FILE])) {
                        $sql->vars($field->name, $field->value->value());
                        $field->value->save();
                    }

                    $queryFields .= $field->name . ', ';
                    $queryValues .= ':' . $field->name . ', ';
                }
            }
        }

        /** Execution of the query */
        $queryFields = substr(trim($queryFields, ','), 0, strlen($queryFields) - 2);
        $queryValues = substr(trim($queryValues, ','), 0, strlen($queryValues) - 2);

        $query = 'INSERT INTO ' . $this->_name . '(' . $queryFields . ') VALUES(' . $queryValues . ')';

        $sql->query('insert_' . $this->_name . '_' . $this->_token, $query);
        $sql->fetch('insert_' . $this->_name . '_' . $this->_token, Sql::PARAM_FETCHINSERT);

        /** Update primary key */
        $this->_fields[$this->_primary]->value = Database::instance()->db()->lastInsertId();

        /** ######################################################################## */
        /** One to many and many to many need more queries after the principal query */

        /** @var $field \Gcs\Framework\Core\Orm\Entity\Field */
        foreach ($fieldsInsertOneToMany as $fields) {
            $relation = '';

            /** @var $relationFields \Gcs\Framework\Core\Orm\Entity\Field */
            foreach ($fields->fields() as $relationFields) {
                if ($relationFields->foreign != null) {
                    if ($relationFields->foreign->referenceEntity() == ucfirst(strtolower($this->_name))) {
                        $relation = $relationFields->foreign->field();
                    }
                }
            }

            $fields->set($relation, $this);
            $fields->insert();
        }

        foreach ($fieldsUpdateOneToMany as $fields) {
            $relation = '';

            /** @var $relationFields \Gcs\Framework\Core\Orm\Entity\Field */
            foreach ($fields->fields() as $relationFields) {
                if ($relationFields->foreign != null) {
                    if ($relationFields->foreign->referenceEntity() == ucfirst(strtolower($this->_name))) {
                        $relation = $relationFields->foreign->field();
                    }
                }
            }

            $fields->set($relation, $this);
            $fields->update();
        }

        if ($manyToMany != null) {
            $currentEntity = $manyToMany->foreign->entity();
            $currentField = $manyToMany->foreign->field();
            $referenceEntity = $manyToMany->foreign->referenceEntity();
            $referenceField = $manyToMany->foreign->referenceField();

            $current = strtolower($currentEntity . $currentField);
            $reference = strtolower($referenceEntity . $referenceField);
            $table = [$current, $reference];
            sort($table, SORT_STRING);
            $table = ucfirst($table[0] . $table[1]);

            $class = '\Orm\Entity\\' . $manyToMany->foreign->referenceEntity();
            /** @var $referencedEntity \Gcs\Framework\Core\Orm\Entity\Entity */
            $referencedEntity = new $class();

            $sql = new Sql();
            $sql->query('orm-delete-many', 'DELETE FROM ' . $table . ' WHERE ' . $this->name() . '_' . $manyToMany->foreign->referenceField() . ' = ' . $this->_fields[$this->_primary]->value);
            $sql->fetch('orm-delete-many');

            /** @var $fields \Gcs\Framework\Core\Orm\Entity\Entity */
            foreach ($fieldsInsertManyToMany as $fields) {
                $fields->set($manyToMany->foreign->field(), $this);
                $fields->insert();

                $sql->query('orm-insert-many', 'INSERT INTO ' . $table . '(' . $this->name() . '_' . $manyToMany->foreign->referenceField() . ', ' . $referencedEntity->name() . '_' . $manyToMany->foreign->field() . ') VALUES (\'' . $this->_fields[$this->_primary]->value . '\', \'' . $fields->get($manyToMany->foreign->field()) . '\')');
                $sql->fetch('orm-insert-many');
            }

            /** @var $fields \Gcs\Framework\Core\Orm\Entity\Entity */
            foreach ($fieldsUpdateManyToMany as $fields) {
                $sql->query('orm-update-many', 'INSERT INTO ' . $table . '(' . $this->name() . '_' . $manyToMany->foreign->referenceField() . ', ' . $referencedEntity->name() . '_' . $manyToMany->foreign->field() . ') VALUES (\'' . $this->_fields[$this->_primary]->value . '\', \'' . $fields->get($manyToMany->foreign->field()) . '\')');
                $sql->fetch('orm-update-many');
            }
        }

        if (!$transaction) {
            Database::instance()->db()->commit();
        }
    }
1169020
private function fixtureObject(array $data): void
    {
        $obj = $this->createNewInstance($data);

        // Sometimes keys are specified in fixtures.
        // We must make sure Doctrine will force them to be saved.
        // Support for non-composite primary keys only.
        // /!\ Be careful, this will override the generator type for ALL objects of the same entity class!
        //     This means that it _may_ break objects for which ids are not provided in the fixtures.
        $metadata = $this->manager->getClassMetadata($this->getEntityClass());
        $primaryKey = $metadata->getIdentifierFieldNames();
        if (1 === \count($primaryKey) && isset($data[$primaryKey[0]])) {
            $metadata->setIdGeneratorType($metadata::GENERATOR_TYPE_NONE);
            $metadata->setIdGenerator(new AssignedGenerator());
        }

        // And finally we persist the item
        $this->manager->persist($obj);

        // If we need to flush it, then we do it too.
        if (
            $this->numberOfIteratedObjects > 0
            && $this->flushEveryXIterations() > 0
            && $this->numberOfIteratedObjects % $this->flushEveryXIterations() === 0
        ) {
            $this->manager->flush();
            if ($this->clearEMOnFlush) {
                $this->manager->clear();
            }
        }

        // If we have to add a reference, we do it
        if ($prefix = $this->getReferencePrefix()) {
            $methodName = $this->getMethodNameForReference();

            $reference = null;

            if (method_exists($obj, $methodName)) {
                $reference = $obj->{$methodName}();
            } elseif (method_exists($obj, '__toString')) {
                $reference = (string) $obj;
            }

            if (!$reference) {
                throw new RuntimeException(sprintf(
                    'If you want to specify a reference with prefix "%s", method "%s" or "%s" must exist in the class, or you can override the "%s" method and add your own.',
                    $prefix, $methodName, '__toString()', 'getMethodNameForReference'
                ));
            }
            $this->addReference($prefix.$reference, $obj);
        }
    }
236466
private function generateManyManyRelations($table, $fks, $relations)
    {
        $db = $this->getDbConnection();

        foreach ($fks as $pair) {
            list($firstKey, $secondKey) = $pair;
            $table0 = $firstKey[0];
            $table1 = $secondKey[0];
            unset($firstKey[0], $secondKey[0]);
            $className0 = $this->generateClassName($table0);
            $className1 = $this->generateClassName($table1);
            $table0Schema = $db->getTableSchema($table0);
            $table1Schema = $db->getTableSchema($table1);

            // @see https://github.com/yiisoft/yii2-gii/issues/166
            if ($table0Schema === null || $table1Schema === null) {
                continue;
            }

            $link = $this->generateRelationLink(array_flip($secondKey));
            $viaLink = $this->generateRelationLink($firstKey);
            $relationName = $this->generateRelationName($relations, $table0Schema, key($secondKey), true);
            $relations[$table0Schema->fullName][$relationName] = [
                "return \$this->hasMany($className1::className(), $link)->viaTable('"
                . $this->generateTableName($table->name) . "', $viaLink);",
                $className1,
                true,
            ];

            $link = $this->generateRelationLink(array_flip($firstKey));
            $viaLink = $this->generateRelationLink($secondKey);
            $relationName = $this->generateRelationName($relations, $table1Schema, key($firstKey), true);
            $relations[$table1Schema->fullName][$relationName] = [
                "return \$this->hasMany($className0::className(), $link)->viaTable('"
                . $this->generateTableName($table->name) . "', $viaLink);",
                $className0,
                true,
            ];
        }

        return $relations;
    }
518547
static function ClassHasMany($parentclass, $foreignRef, $foreignKey = false, $foreignClass = 'ADODB_Active_Record')
	{
		$ar = new $parentclass();
		$ar->hasMany($foreignRef, $foreignKey, $foreignClass);
	}
583924
public function validateReferenceable()
    {
        if ($this->uuidFieldName && !$this->referenceable && 'full' !== $this->versionable) {
            throw MappingException::notReferenceable($this->name, $this->uuidFieldName);
        }
    }
321670
public function belongsToMany(
        $entity,
        string $related,
        string $table = null,
        string $foreignKey = null,
        string $otherKey = null
    ): BelongsToMany {
        // Add the relation to the definition in map
        list(, $caller) = debug_backtrace(false);
        $relation = $caller['function'];
        $this->relatedClasses[$relation] = $related;

        $this->addManyRelation($relation);
        $this->addForeignRelation($relation);
        $this->addPivotRelation($relation);

        // First, we'll need to determine the foreign key and "other key" for the
        // relationship. Once we have determined the keys we'll make the query
        // instances as well as the relationship instances we need for this.
        $foreignKey = $foreignKey ?: $this->getForeignKey();

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

        $relatedMap = $relatedMapper->getEntityMap();

        $otherKey = $otherKey ?: $relatedMap->getForeignKey();

        // If no table name was provided, we can guess it by concatenating the two
        // models using underscores in alphabetical order. The two model names
        // are transformed to snake case from their default CamelCase also.
        if (is_null($table)) {
            $table = $this->joiningTable($relatedMap);
        }

        return new BelongsToMany($relatedMapper, $entity, $table, $foreignKey, $otherKey, $relation);
    }
310314
function setReference( $table, $name, $key ) {

        $this->references[ $table ][ $name ] = $key;

        return $this;

    }
255472
public function equals( $reference ) {
        if ( !( $reference instanceof EntityReference ) ) {
            return false;
        }

        if ( strcasecmp( $this->entityLogicalName, $reference->entityLogicalName ) !== 0 ) {
            return false;
        }

        if ( $this->getID() !== $reference->getID() ) {
            return false;
        }

        return true;
    }
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);
        }
    }
869766
public function add($fk, $column = null, $keyName = null, $onDelete = null, $onUpdate = null)
    {
        $baseFk = $this->baseFk($fk);

        $column = $column ?: $baseFk->defaultColumn();

        static::$foreignKeys[] = [
            'column'          => $column ?: $this->column,
            'key_name'        => $keyName ?: $this->keyName,
            'table'           => $this->table->getTable(),
            'reference_table' => $baseFk->referenceTable(),
            'primary_key'     => $baseFk->primaryKey,
            'on_delete'       => $onDelete ?: $this->onDelete ?: $baseFk->onDelete,
            'on_update'       => $onUpdate ?: $this->onUpdate ?: $baseFk->onUpdate,
        ];

        return $baseFk->createFkColumn($column);
    }
537001
public function addReference(ChildReference $reference)
    {
        if ($this->collReferences === null) {
            $this->initReferences();
        }

        if (!$this->getReferences()->contains($reference)) {
            // only add it if the **same** object is not already associated
            $this->collReferences->push($reference);
            $this->doAddReference($reference);
        }

        return $this;
    }
1103926
public function asEntity()
    {
        call_user_func($this->callback, $this->propertyName, function (EntityRelation $relation) {
            /** @var IEntityMapper $entityMapper */
            $entityMapper = $relation->getMapper();
            if ($relation instanceof IToOneRelation) {
                return new ToOneRelationObjectReference($entityMapper);
            } else {
                return new ToManyRelationObjectReference($entityMapper);
            }
        });
    }
804556
public function resolve(Constraint $constraint, FormInterface $form, RuleCollection $collection)
    {
        /** @var \Symfony\Component\Validator\Constraints\AbstractComparison $constraint */
        $constraintClass = get_class($constraint);
        if ($constraintClass === LessThan::class) {
            $rule = new ConstraintRule(
                self::RULE_NAME,
                $constraint->value - 1, // TODO support floats
                new RuleMessage($constraint->message, array('{{ compared_value }}' => $constraint->value)),
                $constraint->groups
            );
        } elseif ($constraintClass === LessThanOrEqual::class) {
            $rule = new ConstraintRule(
                self::RULE_NAME,
                $constraint->value,
                new RuleMessage($constraint->message, array('{{ compared_value }}' => $constraint->value)),
                $constraint->groups
            );
        }
        /** @var Range $constraint */
        elseif ($constraintClass === Range::class && $constraint->max !== null) {
            $rule = new ConstraintRule(
                self::RULE_NAME,
                $constraint->max,
                new RuleMessage($constraint->maxMessage, array('{{ limit }}' => $constraint->max)),
                $constraint->groups
            );
        } else {
            throw new LogicException();
        }

        $collection->set(
            self::RULE_NAME,
            $rule
        );
    }