ChatGPT解决这个技术问题 Extra ChatGPT

Preventing Laravel adding multiple records to a pivot table

I have a many to many relationship set up and working, to add an item to the cart I use:

$cart->items()->attach($item);

Which adds an item to the pivot table (as it should), but if the user clicks on the link again to add an item they have already added it creates a duplicate entry in the pivot table.

Is there a built in way to add a record to a pivot table only if one does not already exist?

If not, how can I check the pivot table to find if a matching record already exists?


B
Barryvdh

You can also use the $model->sync(array $ids, $detaching = true) method and disable detaching (the second param).

$cart->items()->sync([$item->id], false);

Update: Since Laravel 5.3 or 5.2.44, you can also call syncWithoutDetaching:

$cart->items()->syncWithoutDetaching([$item->id]);

Which does exactly the same, but more readable :)


The second boolean parameter to prevent detaching unlisted IDs is not in the main L5 documentation. It is very good to know - seems to be the only way to "attach if not already attached" in a single statement.
This should be accepted as the answer, it is a much better way to do it than the accepted answer
For newbies like me: $cart->items()->sync([1, 2, 3]) will construct many-to-many relationships to the id's in the given array, 1, 2, and 3, and delete (or "detach") all other id's not in the array. This way, only the id's given in the array will exist in the table. Brilliant @Barryvdh uses an undocumented second parameter to disable that detaching, so no relationships not in the given array are deleted but only unique id's will be attached. Check out the "syncing for convienence" doc. (Laravel 5.2)
FYI, I updated the docs, so 5.2 and above: laravel.com/docs/5.2/… And Taylor immediately added add syncWithoutDetaching(), which calls the sync() with false as second param.
Laravel 5.5 laravel.com/docs/5.5/… syncWithoutDetaching() worked!
A
Alexandre Butynski

You can check the presence of an existing record by writing a very simple condition like this one :

if (! $cart->items->contains($newItem->id)) {
    $cart->items()->save($newItem);
}

Or/and you can add unicity condition in your database, it would throw an exception during an attempt of saving a doublet.

You should also take a look at the more straightforward answer from Barryvdh.


The $id parameter for the method attach() is mixed, it can be an int or instance of model ;) - see github.com/laravel/framework/blob/master/src/Illuminate/…
@bagusflyer contains statement check if the key is present in one object in the collection. You should have a mistake in your code.
I don't like this solution because it forces an extra query ($cart->items) I've been doing something like: $cart->items()->where('foreign_key', $foreignKey)->count() Which, well, actually performs an additional query too '^^ But I don't need to fetch and hydrate the whole collection unless I really need it.
That's right, your solution is a bit more optimized. You can even use the exists() function instead of count() for the best optimization.
and what about when you're removing some relations? this just adds new relations
O
Octavian Ruda

@alexandre Butynsky method works very well but use two sql queries.

One to check if cart contains the item and one to save.

To use only one query use this:

try {
    $cart->items()->save($newItem);
}
catch(\Exception $e) {}

This is what I did in my project. But the problem is you don't know if this exception is caused by the duplicated entry or anything else.
why would it throw any exception? it would be if there's some unique key
a
adeguk Loggcity

As good as all this answers are because I had tried them all, one thing is still left unanswer or not taken care of: the issue of updating a previously checked value (unchecked the checked box[es]). I do have something similar to the above question expect i want to check and uncheck features of products in my product-feature table (the pivot table). I am a newbie and I have realised none of the above did that. The are both good when adding new features but not when i want to remove existing features (i.e. uncheck it)

I will appreciate any enlightenment in to this.

$features = $request->get('features');

if (isset($features) && Count($features)>0){
    foreach ($features as $feature_id){
        $feature = Feature::whereId($feature_id)->first();
        $product->updateFeatures($feature);
    }
}

//product.php (extract)
public function updateFeatures($feature) {
        return $this->features()->sync($feature, false);
}

or

public function updateFeatures($feature) {
   if (! $this->features->contains($features))
        return $this->features()->attach($feature);
}
//where my attach() is:
public function addFeatures($feature) {
        return $this->features()->attach($feature);
}

Sorry guys, not sure is I should delete the question because having figure out the answer myself, it sounds a bit stupid, well the answer to the above is as simple as working @Barryvdh sync() as follows; having read more and more about:

$features = $request->get('features');
if (isset($features) && Count($features)>0){
    $product->features()->sync($features);
}

P
Peter de Kok

There are some great answers posted already. I wanted to throw this one out here as well though.

The answers from @AlexandreButynski and @Barryvdh are more readable than my suggestion, what this answer adds is some efficiency.

It retrieves only the entries for the current combination (actually only the id) and it than attaches it if it does not exist. The sync method (even without detaching) retrieves all currently attached ids. For smaller sets with little iterations this will hardly be a difference, ... you get my point.

Anyway, it is definitely not as readable, but it does the trick.

if (is_null($book->authors()->find($author->getKey(), [$author->getQualifiedKeyName()])))
    $book->authors()->attach($author);

This is much more performant when working with large relationships. Our server was running out of memory when using the other methods