Coder Perfect

Preventing Laravel from populating a pivot table with multiple records

Problem

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

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

This adds an item to the pivot table (as it should), however it creates a duplicate entry in the pivot table if the user clicks on the link again to add an item they’ve previously added.

Is there a method to add a record to a pivot table only if there isn’t one already?

If not, how can I see if a matching entry already exists in the pivot table?

Asked by Al_

Solution #1

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

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

You can now call syncWithoutDetaching: since Laravel 5.3 or 5.2.44.

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

Which accomplishes the same goal, but in a more readable manner:)

Answered by Barryvdh

Solution #2

You may use a simple condition like this to check for the existence of an existing record:

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

You can also add a unicity condition to your database, which will throw an exception if you try to save a doublet.

You should also have a look at Barryvdh’s more basic response.

Answered by Alexandre Butynski

Solution #3

@alexandre The Butynsky approach is quite effective, although it requires two SQL queries.

One to see if the item is in the cart, and one to save.

Utilize this if you just want to use one query:

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

Answered by Octavian Ruda

Solution #4

As good as all of these solutions are because I tried them all, one question remains unanswered or unaddressed: how to update a previously checked value (unchecked the checked box[es]). I have a question that is identical to the one above, only I want to be able to check and uncheck product features in my product-feature table (the pivot table). I’m a novice, and none of the options above achieved that for me. They’re both great for introducing new features, but not so much for removing old ones (i.e. uncheck it)

Any insight into this would be greatly appreciated.

$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 folks, I’m not sure whether I should delete the question because I figured out the answer myself, but the answer to the following is as simple as using @Barryvdh sync() as follows; having read more and more about:

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

Answered by adeguk Loggcity

Solution #5

There have already been some excellent responses. However, I wanted to include this one as well.

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

It merely fetches the entries for the current combination (really only the id) and attaches them if they don’t already exist. Even without disconnecting, the sync method collects all presently attached ids. This won’t make much of a difference for smaller sets with few iterations,… you get the idea.

Anyway, it’s not as readable, but it gets the job done.

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

Answered by Peter de Kok

Post is based on https://stackoverflow.com/questions/17472128/preventing-laravel-adding-multiple-records-to-a-pivot-table