Load new models from POST for many-to-many relation with viaTable

@param $attribute
@throws RelationException
326138
protected function loadModelsManyToManyViaTable($attribute)
    {
        $data = $this->relationalData[$attribute];

        $activeQuery = $data['activeQuery'];
        /** @var ActiveRecord $class */
        $class = $activeQuery->modelClass;

        $via = $activeQuery->via;
        $data['junctionTable'] = $via->from[0];

        list($data['junctionColumn']) = array_keys($via->link);
        list($data['relatedColumn']) = array_values($activeQuery->link);
        $junctionColumn = $data['junctionColumn'];
        $relatedColumn = $data['relatedColumn'];

        if (!empty($data['data'])) {
            // make sure what all row's ids from POST exists in database
            $countManyToManyModels = $class::find()->where([$class::primaryKey()[0] => $data['data']])->count();
            if ($countManyToManyModels != count($data['data'])) {
                throw new RelationException('Related records for attribute ' . $attribute . ' not found');
            }
            // create new junction rows
            foreach ($data['data'] as $relatedModelId) {
                $junctionModel = array_merge(
                    !ArrayHelper::isAssociative($via->on) ? [] : $via->on,
                    [$junctionColumn => $this->owner->getPrimaryKey()]
                );
                $junctionModel[$relatedColumn] = $relatedModelId;
                if (isset($this->relations[$attribute]) && is_callable($this->relations[$attribute])) {
                    $junctionModel = call_user_func($this->relations[$attribute], $junctionModel, $relatedModelId);
                }
                $data['newRows'][] = $junctionModel;
            }
        }

        if (!empty($this->owner->getPrimaryKey())) {
            $data['oldRows'] = (new Query())
                ->from($data['junctionTable'])
                ->select(array_merge(
                        [$junctionColumn, $relatedColumn],
                        !ArrayHelper::isAssociative($via->on) ? [] : array_keys($via->on))
                )->where(array_merge(
                    !ArrayHelper::isAssociative($via->on) ? [] : $via->on,
                    [$junctionColumn => $this->owner->getPrimaryKey()]
                ))->all();
        }

        $this->relationalData[$attribute] = $data;
    }
381913
public function getRelationData($attribute)
    {
        return isset($this->relationalData[$attribute]) ? $this->relationalData[$attribute]['data'] : null;
    }
688306
protected function takeAttributeFromDOM($attribute)
    {
        switch ($attribute->localName) {
        case 'relationship':
            $this->_relationship = $attribute->nodeValue;
            break;
        case 'type':
            $this->_type = $attribute->nodeValue;
            break;
        default:
            parent::takeAttributeFromDOM($attribute);
        }
    }
142525
public function loadRelation($relation)
    {
        //If the node isn't already loaded
        if(!isset($this->relationProxyCache[$relation->getId()]))
        {
            //Create a proxy entity
            $em = $this;
            $entity = $this->proxyFactory->fromRelation($relation, $this->metaRepository, function ($node) use ($em) {
                return $em->loadNode($node);
            });

            $this->relationProxyCache[$relation->getId()] = $entity;
            $this->everymanRelationCache[$this->getHash($entity)] = $relation;
        }

        return $this->relationProxyCache[$relation->getId()];
    }
894274
protected function fillAttributeFromRequest(NovaRequest $request, $requestAttribute, $model, $attribute)
    {
        foreach ($this->meta['types'] as $type) {

            if($request->get($attribute) == $type['value']) {
                $relatedModel = new $type['value'];

                if($this->mapToKey($type['value']) == $model->{$this->attribute . '_type'}) {
                    $relatedModel = $relatedModel->newQuery()->findOrFail($model->{$this->attribute . '_id'});
                } elseif(! is_null($model->{$this->attribute . '_type'})) {
                    $oldRelatedClass = $this->mapToClass($model->{$this->attribute . '_type'});
                    $oldRelatedModel = (new $oldRelatedClass)->newQuery()->findOrFail($model->{$this->attribute . '_id'});
                    $oldRelatedModel->delete();
                }

                foreach ($type['fields'] as $field) {
                    $field->fill($request, $relatedModel);
                }

                $relatedModel->save();

                $model->{$this->attribute.'_id'} = $relatedModel->id;
                $model->{$this->attribute.'_type'} = $this->mapToKey($type['value']);
            }

        }
    }
520675
protected function takeAttributeFromDOM($attribute)
    {
        switch ($attribute->localName) {
        case 'url':
            $this->_url = $attribute->nodeValue;
            break;
        default:
            parent::takeAttributeFromDOM($attribute);
        }
    }
883292
public function loadRelations() {
        foreach ([
            Relation::MANY_TO_ONE => $this->belongsTo,
            Relation::MANY_TO_MANY => $this->belongsToMany,
            Relation::ONE_TO_ONE => $this->hasOne,
            Relation::ONE_TO_MANY => $this->hasMany
        ] as $type => $relations) {
            foreach ($relations as $alias => $relation) {
                if (is_string($relation)) {
                    $relation = ['model' => $relation];
                }

                $relation = $relation + [
                    'model' => null,
                    'foreignKey' => null,
                    'relatedForeignKey' => null,
                    'conditions' => null,
                    'junction' => null
                ];

                switch ($type) {
                    case Relation::MANY_TO_ONE:
                        $this->belongsTo($alias, $relation['model'], $relation['foreignKey'], $relation['conditions']);
                    break;
                    case Relation::MANY_TO_MANY:
                        $this->belongsToMany($alias, $relation['model'], $relation['junction'], $relation['foreignKey'], $relation['relatedForeignKey'], $relation['conditions']);
                    break;
                    case Relation::ONE_TO_ONE:
                        $this->hasOne($alias, $relation['model'], $relation['relatedForeignKey'], $relation['conditions']);
                    break;
                    case Relation::ONE_TO_MANY:
                        $this->hasMany($alias, $relation['model'], $relation['relatedForeignKey'], $relation['conditions']);
                    break;
                }
            }
        }

        return $this;
    }
1337419
protected function checkAttributeAssignable($attribute): void
    {
        if (empty($this->assignable)) {
            return;
        }

        if (is_array($attribute)) {

            foreach ($attribute as $singleAttribute) {
                $this->checkAttributeAssignable($singleAttribute);
            }

            return;
        }

        if ( ! in_array($attribute, $this->assignable)) {
            throw new UnassignableAttributeException("Not allowed to assign value for '{$attribute}'");
        }
    }
7156
protected function updateRelation($relationsData)
    {
        foreach ($relationsData as $name => $values) {
            if (!method_exists($this->model, $name)) {
                continue;
            }

            $relation = $this->model->$name();

            $oneToOneRelation = $relation instanceof Relations\HasOne
                || $relation instanceof Relations\MorphOne
                || $relation instanceof Relations\BelongsTo;

            $prepared = $this->prepareUpdate([$name => $values], $oneToOneRelation);

            if (empty($prepared)) {
                continue;
            }

            switch (true) {
                case $relation instanceof Relations\BelongsToMany:
                case $relation instanceof Relations\MorphToMany:
                    if (isset($prepared[$name])) {
                        $relation->sync($prepared[$name]);
                    }
                    break;
                case $relation instanceof Relations\HasOne:

                    $related = $this->model->$name;

                    // if related is empty
                    if (is_null($related)) {
                        $related = $relation->getRelated();
                        $qualifiedParentKeyName = $relation->getQualifiedParentKeyName();
                        $localKey = Arr::last(explode('.', $qualifiedParentKeyName));
                        $related->{$relation->getForeignKeyName()} = $this->model->{$localKey};
                    }

                    foreach ($prepared[$name] as $column => $value) {
                        $related->setAttribute($column, $value);
                    }

                    $related->save();
                    break;
                case $relation instanceof Relations\BelongsTo:
                case $relation instanceof Relations\MorphTo:

                    $parent = $this->model->$name;

                    // if related is empty
                    if (is_null($parent)) {
                        $parent = $relation->getRelated();
                    }

                    foreach ($prepared[$name] as $column => $value) {
                        $parent->setAttribute($column, $value);
                    }

                    $parent->save();

                    // When in creating, associate two models
                    $foreignKeyMethod = (app()->version() < '5.8.0') ? 'getForeignKey' : 'getForeignKeyName';
                    if (!$this->model->{$relation->{$foreignKeyMethod}()}) {
                        $this->model->{$relation->{$foreignKeyMethod}()} = $parent->getKey();

                        $this->model->save();
                    }

                    break;
                case $relation instanceof Relations\MorphOne:
                    $related = $this->model->$name;
                    if (is_null($related)) {
                        $related = $relation->make();
                    }
                    foreach ($prepared[$name] as $column => $value) {
                        $related->setAttribute($column, $value);
                    }
                    $related->save();
                    break;
                case $relation instanceof Relations\HasMany:
                case $relation instanceof Relations\MorphMany:

                    foreach ($prepared[$name] as $related) {
                        /** @var Relations\Relation $relation */
                        $relation = $this->model()->$name();

                        $keyName = $relation->getRelated()->getKeyName();

                        $instance = $relation->findOrNew(Arr::get($related, $keyName));

                        if ($related[static::REMOVE_FLAG_NAME] == 1) {
                            $instance->delete();

                            continue;
                        }

                        Arr::forget($related, static::REMOVE_FLAG_NAME);

                        $instance->fill($related);

                        $instance->save();
                    }

                    break;
            }
        }
    }
833953
protected function saveModels($attribute)
    {
        $data = $this->relationalData[$attribute];

        /** @var ActiveRecord $model */
        foreach ($data['newModels'] as $model) {
            if ($model->isNewRecord) {
                if (!empty($data['activeQuery']->via)) {
                    // only for many-to-many
                    $junctionColumn = $data['junctionColumn'];
                    $model->$junctionColumn = $this->owner->getPrimaryKey();
                } elseif ($data['activeQuery']->multiple) {
                    // only one-to-many
                    foreach ($data['activeQuery']->link as $childAttribute => $parentAttribute) {
                        $model->$childAttribute = $this->owner->$parentAttribute;
                    }
                }
                if (!$model->save()) {
                    Yii::$app->getDb()->getTransaction()->rollBack();
                    throw new RelationException('Model ' . $model::className() . ' not saved due to unknown error');
                }
            }
        }

        // only for many-to-many
        $this->relationsMap($data['newRows'], function($row) use ($attribute, $data) {
            $junctionColumn = $data['junctionColumn'];
            $row[$junctionColumn] = $this->owner->getPrimaryKey();
            if (!$this->isExistingRow($row, $attribute)) {
                Yii::$app->db->createCommand()
                    ->insert($data['junctionTable'], $row)
                    ->execute();
            }
        });
    }
8792
public function setAttribute($attribute, $value){
        if($attribute === 'table'){
            $this->_table = $value;
        }
        elseif($attribute === 'pk'){
            $this->_pk = $value;
        }
        elseif($attribute === 'relations'){
            $this->_relations = $value;
        }
        
        return $this;
    }
753860
public function addAttribute($attribute) {
      $args = func_get_args();
      $cl = get_class($this);
      if (is_array($attribute))
        $attribute = new BaMLAttribute($attribute[0], $attribute[1]);
      if (is_string($attribute))
        $attribute = new BaMLAttribute($args[0], $args[1]);
      if (!($attribute instanceof BaMLAttribute))
        throw new \CatapultApiException("You can only add type BaMLAttribute or array..");
      if (isset($cl::$params) && !(in_array($attribute->getKey(), $cl::$params)) && sizeof($cl::$params) > 0)
        throw new \CatapultApiException("attribute '" . $attribute->getKey() . "' is not a valid attribute for verb " . $this->getName() . "" . " please use any of the following: " . $this->printAttributes());
      $this->attributes[] = $attribute;
    }
989131
protected function shouldAttributeBeFilterable(ModelAttributeDataInterface $attribute)
    {
        // If an attribute is a foreign key, it shouldn't be a filter by default.

        foreach ($this->info->relations as $key => $relation) {

            switch ($relation->type) {

                case RelationType::MORPH_TO:
                case RelationType::BELONGS_TO:
                case RelationType::BELONGS_TO_THROUGH:

                    $keys = $relation->foreign_keys ?: [];

                    if (in_array($attribute->name, $keys)) {
                        return false;
                    }
                    break;
            }
        }

        return true;
    }
588931
public function addAttribute(AttributeMetadata $attribute)
    {
        if (isset($this->relationships[$attribute->getKey()])) {
            throw MetadataException::fieldKeyInUse('attribute', 'relationship', $attribute->getKey(), $this->type);
        }
        $this->attributes[$attribute->getKey()] = $attribute;
        ksort($this->attributes);
        return $this;
    }
642960
protected function loadModelsOneToMany($attribute)
    {
        $data = $this->relationalData[$attribute];
        
        $activeQuery = $data['activeQuery'];
        $class = $activeQuery->modelClass;

        // default query conditions
        $params = !ArrayHelper::isAssociative($activeQuery->on) ? [] : $activeQuery->on;
        // one-to-many
        foreach ($activeQuery->link as $childAttribute => $parentAttribute) {
            $params[$childAttribute] = $this->owner->$parentAttribute;
        }

        if (!empty($data['data'])) {
            foreach ($data['data'] as $attributes) {
                $model = new $class(
                    array_merge(
                        $params,
                        ArrayHelper::isAssociative($attributes) ? $attributes : []
                    )
                );
                if (isset($this->relations[$attribute]) && is_callable($this->relations[$attribute])) {
                    $model = call_user_func($this->relations[$attribute], $model, $attributes);
                }
                $data['newModels'][] = $model;
            }
        }

        $this->relationalData[$attribute] = $data;
    }
21549
public function set($attribute, $value)
    {
        list($relationArray, $column, $jsonArray) = FieldNameSlicer::split($attribute);
        $column = $jsonArray ? ($column . '->' . join('->', $jsonArray)) : $column;

        if (count($relationArray) === 0) {
            $this->data->setAttribute($column, $value);

            return;
        }

        $relation = join('.', $relationArray);
        $related = $this->data->getRelationValue($relation);
        if ($related && $related instanceof Model) {
            $related->setAttribute($column, $value);
            $this->relations[$relation] = $related;
        }
    }
322704
protected function loadBelongsTo(
        Collection $relations,
        BelongsTo $relationClass,
        Model $relationModel,
        Model $model,
        Builder $query,
        $direction
    ) {
        $foreignKey = $relationClass->getOwnerKey();
        $ownerKey = $relationClass->getForeignKey();

        $ownerTable = $model->getTable();
        $foreignTable = $relationModel->getTable();

        $ownerColumn = implode('.', [$ownerTable, $ownerKey]);
        $foreignColumn = implode('.', [$foreignTable, $foreignKey]);
        $sortedColumnRaw = '`'.$foreignTable.'`.`'.$relations->last().'`';
        $sortedColumnAlias = implode('__', [$foreignTable, $relations->last()]);

        $this->sortedColumnAlias = $sortedColumnAlias;

        $query
            ->addSelect([DB::raw($sortedColumnRaw.' AS '.$sortedColumnAlias)])
            ->join($foreignTable, $foreignColumn, '=', $ownerColumn, 'left');
    }