src/Resources/DbSchemaResource.php
public function buildTableRelated(
        $table_schema,
        $related,
        $allow_update = false,
        $allow_delete = false
    ) {
        if (!is_array($related) || empty($related)) {
            throw new \Exception('There are no related elements in the requested schema.');
        }

        if (!isset($related[0])) {
            // single record possibly passed in without wrapper array
            $related = [$related];
        }

        $extras = [];
        $dropExtras = [];
        $virtuals = [];
        $dropVirtuals = [];
        foreach ($related as $relation) {
            $this->cleanClientRelation($relation);
            $name = array_get($relation, 'name');

            /** @type RelationSchema $oldRelation */
            if (!empty($name) && ($oldRelation = $table_schema->getRelation($name))) {
                // UPDATE
                if (!$allow_update) {
                    throw new \Exception("Relation '$name' already exists in table '{$table_schema->name}'.");
                }

                $oldArray = $oldRelation->toArray();
                $extraNew = array_only($relation, $this->relatedExtras);
                $extraOld = array_only($oldArray, $this->relatedExtras);
                if (!empty($extraNew = array_diff_assoc($extraNew, $extraOld))) {
                    // if all empty, delete the extras entry, otherwise update
                    $combined = array_merge($extraOld, $extraNew);
                    if (!empty($combined['alias']) || !empty($combined['label']) || !empty($combined['description']) ||
                        !empty($combined['always_fetch']) || !empty($combined['flatten']) ||
                        !empty($combined['flatten_drop_prefix'])
                    ) {
                        $extraNew['table'] = $table_schema->name;
                        $extraNew['relationship'] = $name;
                        $extras[] = $extraNew;
                    } else {
                        $dropExtras[$table_schema->name][] = $oldRelation->name;
                    }
                }

                // only virtual
                if (boolval(array_get($relation, 'is_virtual'))) {
                    // clean out extras
                    $noDiff = array_merge($this->relatedExtras, ['native']);
                    $relation = array_except($relation, $noDiff);
                    if (!empty(array_diff_assoc($relation, array_except($oldArray, $noDiff)))) {
                        $relation['table'] = $table_schema->name;
                        $virtuals[] = $relation;
                    }
                }
            } else {
                // CREATE
                // clean out extras
                $extraNew = array_only($relation, $this->relatedExtras);
                if (!empty($extraNew['alias']) || !empty($extraNew['label']) || !empty($extraNew['description']) ||
                    !empty($extraNew['always_fetch']) || !empty($extraNew['flatten']) ||
                    !empty($extraNew['flatten_drop_prefix'])
                ) {
                    if (!empty($si = array_get($relation, 'ref_service_id'))) {
                        if ($this->getServiceId() !== $si) {
                            $relation['ref_service'] = ServiceManager::getServiceNameById($si);
                        }
                    }
                    if (!empty($si = array_get($relation, 'junction_service_id'))) {
                        if (!empty($si) && ($this->getServiceId() !== $si)) {
                            $relation['junction_service'] = ServiceManager::getServiceNameById($si);
                        }
                    }
                    $extraNew['table'] = $table_schema->name;
                    $extraNew['relationship'] = RelationSchema::buildName($relation);
                    $extras[] = $extraNew;
                }

                // only virtual
                if (boolval(array_get($relation, 'is_virtual'))) {
                    $relation = array_except($relation, $this->relatedExtras);
                    $relation['table'] = $table_schema->name;
                    $virtuals[] = $relation;
                } else {
                    // todo create foreign keys here eventually as well?
                }
            }
        }

        if ($allow_delete && isset($oldSchema)) {
            // check for relations to drop
            /** @type RelationSchema $oldField */
            foreach ($oldSchema->getRelations() as $oldRelation) {
                $found = false;
                foreach ($related as $relation) {
                    $relation = array_change_key_case($relation, CASE_LOWER);
                    if (array_get($relation, 'name') === $oldRelation->name) {
                        $found = true;
                    }
                }
                if (!$found) {
                    if ($oldRelation->isVirtual) {
                        $dropVirtuals[$table_schema->name][] = $oldRelation->toArray();
                    } else {
                        $dropExtras[$table_schema->name][] = $oldRelation->name;
                    }
                }
            }
        }

        return [
            'related_extras'         => $extras,
            'drop_related_extras'    => $dropExtras,
            'virtual_relations'      => $virtuals,
            'drop_virtual_relations' => $dropVirtuals,
        ];
    }
public function buildTableRelated(
        $table_schema,
        $related,
        $allow_update = false,
        $allow_delete = false
    ) {
        if (!is_array($related) || empty($related)) {
            throw new \Exception('There are no related elements in the requested schema.');
        }

        if (!isset($related[0])) {
            // single record possibly passed in without wrapper array
            $related = [$related];
        }

        $extras = [];
        $dropExtras = [];
        $virtuals = [];
        $dropVirtuals = [];
        foreach ($related as $relation) {
            $this->cleanClientRelation($relation);
            $name = array_get($relation, 'name');

            /** @type RelationSchema $oldRelation */
            if (!empty($name) && ($oldRelation = $table_schema->getRelation($name))) {
                // UPDATE
                if (!$allow_update) {
                    throw new \Exception("Relation '$name' already exists in table '{$table_schema->name}'.");
                }

                $oldArray = $oldRelation->toArray();
                $extraNew = array_only($relation, $this->relatedExtras);
                $extraOld = array_only($oldArray, $this->relatedExtras);
                if (!empty($extraNew = array_diff_assoc($extraNew, $extraOld))) {
                    // if all empty, delete the extras entry, otherwise update
                    $combined = array_merge($extraOld, $extraNew);
                    if (!empty($combined['alias']) || !empty($combined['label']) || !empty($combined['description']) ||
                        !empty($combined['always_fetch']) || !empty($combined['flatten']) ||
                        !empty($combined['flatten_drop_prefix'])
                    ) {
                        $extraNew['table'] = $table_schema->name;
                        $extraNew['relationship'] = $name;
                        $extras[] = $extraNew;
                    } else {
                        $dropExtras[$table_schema->name][] = $oldRelation->name;
                    }
                }

                // only virtual
                if (boolval(array_get($relation, 'is_virtual'))) {
                    // clean out extras
                    $noDiff = array_merge($this->relatedExtras, ['native']);
                    $relation = array_except($relation, $noDiff);
                    if (!empty(array_diff_assoc($relation, array_except($oldArray, $noDiff)))) {
                        $relation['table'] = $table_schema->name;
                        $virtuals[] = $relation;
                    }
                }
            } else {
                // CREATE
                // clean out extras
                $extraNew = array_only($relation, $this->relatedExtras);
                if (!empty($extraNew['alias']) || !empty($extraNew['label']) || !empty($extraNew['description']) ||
                    !empty($extraNew['always_fetch']) || !empty($extraNew['flatten']) ||
                    !empty($extraNew['flatten_drop_prefix'])
                ) {
                    if (!empty($si = array_get($relation, 'ref_service_id'))) {
                        if ($this->getServiceId() !== $si) {
                            $relation['ref_service'] = ServiceManager::getServiceNameById($si);
                        }
                    }
                    if (!empty($si = array_get($relation, 'junction_service_id'))) {
                        if (!empty($si) && ($this->getServiceId() !== $si)) {
                            $relation['junction_service'] = ServiceManager::getServiceNameById($si);
                        }
                    }
                    $extraNew['table'] = $table_schema->name;
                    $extraNew['relationship'] = RelationSchema::buildName($relation);
                    $extras[] = $extraNew;
                }

                // only virtual
                if (boolval(array_get($relation, 'is_virtual'))) {
                    $relation = array_except($relation, $this->relatedExtras);
                    $relation['table'] = $table_schema->name;
                    $virtuals[] = $relation;
                } else {
                    // todo create foreign keys here eventually as well?
                }
            }
        }

        if ($allow_delete && isset($oldSchema)) {
            // check for relations to drop
            /** @type RelationSchema $oldField */
            foreach ($oldSchema->getRelations() as $oldRelation) {
                $found = false;
                foreach ($related as $relation) {
                    $relation = array_change_key_case($relation, CASE_LOWER);
                    if (array_get($relation, 'name') === $oldRelation->name) {
                        $found = true;
                    }
                }
                if (!$found) {
                    if ($oldRelation->isVirtual) {
                        $dropVirtuals[$table_schema->name][] = $oldRelation->toArray();
                    } else {
                        $dropExtras[$table_schema->name][] = $oldRelation->name;
                    }
                }
            }
        }

        return [
            'related_extras'         => $extras,
            'drop_related_extras'    => $dropExtras,
            'virtual_relations'      => $virtuals,
            'drop_virtual_relations' => $dropVirtuals,
        ];
    }
@param TableSchema $table_schema
@param array       $related
@param bool        $allow_update
@param bool        $allow_delete

@throws \Exception
@return array