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 relationship?
posts
: id, name
links
: id, name, post_id
Here, each Post
can have multiple Link
s.
I'd like to synchronize the links associated with a specific post in the database, against an inputted collection of links (for example, from a CRUD form where I can add, remove, and modify links).
Links in the database that aren't present in my input collection should be removed. Links that exist in the database and in my input should be updated to reflect the input, and links that are only present in my input should be added as new records in the database.
To summarize the desired behavior:
hasMany relationship in laravel is used to create the relation between two tables. hasMany means create the relation one to Many. For example if a article have comments and we wanted to get all comments of the article then we can use hasMany relationship .
01 Use Of Sync In Laravel The sync() method accepts an array as an argument. As the name suggests, this method synchronizes the database entries that means whatever you pass in this method, those records will be kept into the database and the rest will be removed from the intermediate(pivot) table.
A one-to-one polymorphic relationship is a situation where one model can belong to more than one type of model but on only one association. A typical example of this is featured images on a post and an avatar for a user. The only thing that changes however is how we get the associated model by using morphOne instead.
Laravel eager loading. What is eager loading? Eager loading is a concept in which when retrieving items, you get all the needed items together with all (or most) related items at the same time. This is in contrast to lazy loading where you only get one item at one go and then retrieve related items only when needed.
Unfortunately there is no sync
method for one-to-many relations. It's pretty simple to do it by yourself. At least if you don't have any foreign key referencing links
. Because then you can simple delete the rows and insert them all again.
$links = array( new Link(), new Link() ); $post->links()->delete(); $post->links()->saveMany($links);
If you really need to update existing one (for whatever reason) you need to do exactly what you described in your question.
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:
<?php namespace App\Model\Relations; use Illuminate\Database\Eloquent\Relations\HasMany; /** * @link https://github.com/laravel/framework/blob/5.4/src/Illuminate/Database/Eloquent/Relations/HasMany.php */ class HasManySyncable extends HasMany { public function sync($data, $deleting = true) { $changes = [ 'created' => [], '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:
<?php namespace App\Model; use App\Model\Relations\HasManySyncable; use Illuminate\Database\Eloquent\Model; abstract class MyBaseModel extends Model { /** * Overrides the default Eloquent hasMany relationship to return a HasManySyncable. * * {@inheritDoc} * @return \App\Model\Relations\HasManySyncable */ public function hasMany($related, $foreignKey = null, $localKey = null) { $instance = $this->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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With