46

Using Laravel, I have the following code

$review = Review::find(1);
$review->delete();

Review has a many to many relationship defined with a Product entity. When I delete a review, I'd expect it to be detached from the associated products in the pivot table, but this isn't the case. When I run the code above, I still see the linking row in the pivot table.

Have I missed something out here or is this the way Laravel works? I'm aware of the detach() method, but I thought that deleting an entity would also detach it from any related entities automatically.

Review is defined like this:

<?php
class Review extends Eloquent
{
    public function products()
    {
        return $this->belongsToMany('Product');
    }
}

Product is defined like this:

<?php
class Product extends Eloquent
{
    public function reviews()
    {
        return $this->belongsToMany('Review');
    }
}

Thanks in advance.

Grant J
  • 1,056
  • 3
  • 10
  • 18
  • This is how `Eloquent` works. You can use DB events for the pivot table (`on delete cascade`) or implement your event handlers using Eloquent. Something like http://stackoverflow.com/a/14174356/784588 – Jarek Tkaczyk Dec 06 '14 at 10:44

6 Answers6

82

The detach method is used to release a relationship from the pivot table, whilst delete will delete the model record itself i.e. the record in the reviews table. My understanding is that delete won't trigger the detach implicitly. You can use model events to trigger a cleanup of the pivot table, though, using something like:

protected static function booted()
{
    static::deleting(function ($review) {
        $review->product()->detach()
    });
}

Also, I would suggest that the relationship would be a one to many, as one product would have many reviews, but one review wouldn't belong to many products (usually).

class Review extends \Eloquent {
    public function product()
    {
        return $this->belongsTo('Product');
    }
}

class Product extends \Eloquent {
    public function reviews()
    {
        return $this->hasMany('Review');
    }
}

Of course, this would then require that you tweak your database structure. If you wanted to leave the database structure and your current relationships as they are, the other option would be to apply a foreign key constraint on the pivot table, such that when either a review or product is removed, you could cascade the delete on the pivot table.

// Part of a database migration
$table->foreign('product_id')->references('id')->on('products')->onDelete('cascade');
$table->foreign('review_id')->references('id')->on('reviews')->onDelete('cascade');

Edit: In adding the constraint, you push the cleanup work onto the database, and don't have to worry about handling it in code.

Aqamarine
  • 19
  • 1
  • 7
Michael Dyrynda
  • 936
  • 7
  • 4
  • In you 1st piece you don't iterate through results, but simply call `$model->relation()->detach()` what will remove all the pivot associations for given `$model` in single query. – Jarek Tkaczyk Dec 06 '14 at 13:54
  • Many thanks. This answers my question thoroughly. By the way, I have a many to many relationship because I have an existing list of product reviews and some people have reviewed multiple products in 1 piece. – Grant J Dec 07 '14 at 07:57
  • Would be awesome to include `detach()` syntax with ID of item to detach, as well as a link to [detach function](http://laravel.com/api/5.1/Illuminate/Database/Eloquent/Relations/BelongsToMany.html#method_detach) or [example](http://laravel.com/docs/5.1/eloquent-relationships#inserting-many-to-many-relationships) – QuickDanger Jul 30 '15 at 07:09
  • The FK constraint is by far the better of the two methods imho. Let the DB handle it all. – stef Feb 08 '18 at 12:55
  • "apply a foreign key constraint on the pivot table, such that when either a review or product is removed, you could cascade the delete on the pivot table." That last part was exactly what a was looking for in another questions and couldn't find, thank you. – Luis David Oct 25 '19 at 13:56
11

Simpler Steps:

In this example an Account has many Tags:

To delete the Tags, then the Account do this:

// delete the relationships with Tags (Pivot table) first.
$account->find($this->accountId)->tags()->detach();

// delete the record from the account table.
$account->delete($this->accountId);

On the Pivot Table make sure you have ->onDelete('cascade');

$table->integer('account_id')->unsigned()->index();
$table->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade');

$table->integer('tag_id')->unsigned()->index();
$table->foreign('tag_id')->references('id')->on('tags')->onDelete('cascade');
Mahmoud Zalt
  • 30,478
  • 7
  • 87
  • 83
6

$review->product()->sync([]) also works.

However $review->product()->detach() is much more explicit.

Christophvh
  • 12,586
  • 7
  • 48
  • 70
Kazuya Gosho
  • 996
  • 13
  • 14
2
public function destroy($id)
{    
    $review = Review::find($id);
    $review ->tagged_users()->detach();
    return $review ->delete(); 
}

also works

0

If that way doesn't solve your problem, you can delete Pivot table records to using the DB option like below ;

DB::table('any_pivot_table')->where('x_column', $xParameter)->delete();

You can solve the problem just like this.

Enjoy your coding !

Enver
  • 542
  • 5
  • 7
-4

i guess you have a error in your models relationship conception, the product has many reviews but the review associated with one product so you have here one to many relationship. but in general the answer in the general case will be using:

$product->reviews()->sync([]);
Hamza Kouadri
  • 143
  • 2
  • 5
  • This answer will remove all pivot table rows for all reviews for a product (assuming the relationship really is many-to-many, which seems a bit strange in this case). It will leave the product and its reviews intact, but not linked. It's a good and useful thing to know, but does not tackle the question at all. – Jason Feb 14 '21 at 17:04