Synchronizing a one-to-many relationship in Laravel

后端 未结 5 719
Happy的楠姐
Happy的楠姐 2021-02-06 23:17

If I have a many-to-many relationship it\'s super easy to update the relationship with its sync method.

But what would I use to synchronize a one-to-many re

5条回答
  •  自闭症患者
    2021-02-07 00:04

    The problem with deleting and readding the related entities, is that it will break any foreign key constraints you might have on those child entities.

    A better solution is to modify Laravel's HasMany relationship to include a sync method:

     [], 'deleted' => [], 'updated' => [],
            ];
    
            $relatedKeyName = $this->related->getKeyName();
    
            // First we need to attach any of the associated models that are not currently
            // in the child entity table. We'll spin through the given IDs, checking to see
            // if they exist in the array of current ones, and if not we will insert.
            $current = $this->newQuery()->pluck(
                $relatedKeyName
            )->all();
        
            // Separate the submitted data into "update" and "new"
            $updateRows = [];
            $newRows = [];
            foreach ($data as $row) {
                // We determine "updateable" rows as those whose $relatedKeyName (usually 'id') is set, not empty, and
                // match a related row in the database.
                if (isset($row[$relatedKeyName]) && !empty($row[$relatedKeyName]) && in_array($row[$relatedKeyName], $current)) {
                    $id = $row[$relatedKeyName];
                    $updateRows[$id] = $row;
                } else {
                    $newRows[] = $row;
                }
            }
    
            // Next, we'll determine the rows in the database that aren't in the "update" list.
            // These rows will be scheduled for deletion.  Again, we determine based on the relatedKeyName (typically 'id').
            $updateIds = array_keys($updateRows);
            $deleteIds = [];
            foreach ($current as $currentId) {
                if (!in_array($currentId, $updateIds)) {
                    $deleteIds[] = $currentId;
                }
            }
    
            // Delete any non-matching rows
            if ($deleting && count($deleteIds) > 0) {
                $this->getRelated()->destroy($deleteIds);    
            }
    
            $changes['deleted'] = $this->castKeys($deleteIds);
    
            // Update the updatable rows
            foreach ($updateRows as $id => $row) {
                $this->getRelated()->where($relatedKeyName, $id)
                     ->update($row);
            }
            
            $changes['updated'] = $this->castKeys($updateIds);
    
            // Insert the new rows
            $newIds = [];
            foreach ($newRows as $row) {
                $newModel = $this->create($row);
                $newIds[] = $newModel->$relatedKeyName;
            }
    
            $changes['created'] = $this->castKeys($newIds);
    
            return $changes;
        }
    
    
        /**
         * Cast the given keys to integers if they are numeric and string otherwise.
         *
         * @param  array  $keys
         * @return array
         */
        protected function castKeys(array $keys)
        {
            return (array) array_map(function ($v) {
                return $this->castKey($v);
            }, $keys);
        }
        
        /**
         * Cast the given key to an integer if it is numeric.
         *
         * @param  mixed  $key
         * @return mixed
         */
        protected function castKey($key)
        {
            return is_numeric($key) ? (int) $key : (string) $key;
        }
    }
    

    You can override Eloquent's Model class to use HasManySyncable instead of the standard HasMany relationship:

    newRelatedInstance($related);
    
            $foreignKey = $foreignKey ?: $this->getForeignKey();
    
            $localKey = $localKey ?: $this->getKeyName();
    
            return new HasManySyncable(
                $instance->newQuery(), $this, $instance->getTable().'.'.$foreignKey, $localKey
            );
        }
    

    Supposing that your Post model extends MyBaseModel and has a links() hasMany relationship, you can do something like:

    $post->links()->sync([
        [
            'id' => 21,
            'name' => "LinkedIn profile"
        ],
        [
            'id' => null,
            'label' => "Personal website"
        ]
    ]);
    

    Any records in this multidimensional array that have an id that matches the child entity table (links) will be updated. Records in the table that are not present in this array will be removed. Records in the array that are not present in the table (Have a non-matching id, or an id of null) will be considered "new" records and will be inserted into the database.

提交回复
热议问题