Martti Laine
Martti Laine

Reputation: 12995

Automatically deleting related rows in Laravel (Eloquent ORM)

When I delete a row using this syntax:

$user->delete();

Is there a way to attach a callback of sorts, so that it would e.g. do this automatically:

$this->photo()->delete();

Preferably inside the model-class.

Upvotes: 225

Views: 415915

Answers (18)

Attila Fulop
Attila Fulop

Reputation: 7011

As of Laravel 5.2, the documentation states that these kinds of event handlers can be registered in the AppServiceProvider:

<?php
class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        User::deleting(function ($user) {
            $user->photos()->delete();
        });
    }

I suggest moving them to separate classes instead of closures for better application structure.

Upvotes: 16

ivanhoe
ivanhoe

Reputation: 4753

I believe this is a perfect use-case for Eloquent events. You can use the "deleting" event to do the cleanup:

<?php

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
    public function photos()
    {
        return $this->has_many('Photo');
    }
    
    // this is a recommended way to declare event handlers
    protected static function booted () {
        static::deleting(function(User $user) { // before delete() method call this
             $user->photos()->delete();
             // do the rest of the cleanup...
        });
    }
}

You should probably also put the whole thing inside a transaction, to ensure the referential integrity..

Upvotes: 306

Rajib Bin Alam
Rajib Bin Alam

Reputation: 389

$table->foreignId('user_id')->constrained('user')->cascadeOnDelete();

or

$table->foreignId('user_id')->constrained()->cascadeOnDelete();

Upvotes: 0

jinglebird
jinglebird

Reputation: 578

  1. Add delete function on model that you want to delete

  2. Define relations of models

for example in this instance:

/**
 * @return bool|null
 */
public function delete(): ?bool
{
    $this->profile()->delete();
    $this->userInterests()->delete();
    $this->userActivities()->delete();
    $this->lastLocation()->delete();

    return parent::delete();
}

And relations in user model are:

public function profile()
{
    return $this->hasOne(Profile::class, 'user_id', 'id');
}

public function userInterests()
{
    return $this->hasMany(userInterest::class, 'user_id', 'id');
}

public function userActivities()
{
    return $this->hasMany(userActivity::class, 'user_id', 'id');
}

public function lastLocation()
{
    return $this->hasOne(LastLocation::class, 'user_id', 'id');
}

Upvotes: 5

Pri Nce
Pri Nce

Reputation: 721

Here are the perfect solutions.

# model

public function order_item_properties()
{
    return $this->hasMany(OrderItemProperty::class, 'order_id', 'id');
}

public function order_variations()
{
    return $this->hasMany(OrderItemVariation::class, 'order_id', 'id');
}

# controller

$order_item = OrderItem::find($request->order_id);

$order_item->order_item_properties()->delete();
$order_item->order_variations()->delete();

$order_item->delete();

return response()->json([
    'message' => 'Deleted',
]);

Upvotes: 0

Leena Patel
Leena Patel

Reputation: 2453

It’s better to use onDelete cascade when defining your model’s migration. This takes care of deleting the model’s relations for you:

e.g.

 $table->foreign(’user_id’)
  ->references(’id’)->on(’users’)
  ->onDelete(’cascade’);

If you happen to find yourself thinking about how to delete a model and its relations to a level greater than 3 or 4 nested relations, then you should consider redefining your model's relationships.

Upvotes: 0

AnasSafi
AnasSafi

Reputation: 6294

This way worked for me on Laravel 8:

public static function boot() {

    parent::boot();
    
    static::deleted(function($item){
        $item->deleted_by = \Auth::id(); // to know who delete item, you can delete this row
        $item->save();  // to know who delete item, you can delete this row
        foreach ($item->photos as $photo){
            $photo->delete();
        }
    });
}

public function photos()
{
    return $this->hasMany('App\Models\Photos');
}

Note: deleting in this syntax $user->photos()->delete(); not worked for me...

Upvotes: 2

SEYED BABAK ASHRAFI
SEYED BABAK ASHRAFI

Reputation: 4281

Using Constrained()

After Laravel 7, new foreignId() and constrained() methods are available for defining relationship constraint in database. OnDelete() method can be used on these methods to automatically delete related records.

Old style

$table->unsignedBigInterer('user_id');

$table->foreign('user_id')
    ->references('id')
    ->on('users')
    ->onDelete('cascade');

New style

$table->foreignId('user_id')
      ->constrained()
      ->onDelete('cascade');

Upvotes: 9

Carlos A. Carneiro
Carlos A. Carneiro

Reputation: 31

I would iterate through the collection detaching everything before deleting the object itself.

here's an example:

try {
        $user = User::findOrFail($id);
        if ($user->has('photos')) {
            foreach ($user->photos as $photo) {

                $user->photos()->detach($photo);
            }
        }
        $user->delete();
        return 'User deleted';
    } catch (Exception $e) {
        dd($e);
    }

I know it is not automatic but it is very simple.

Another simple approach would be to provide the model with a method. Like this:

public function detach(){
       try {
            
            if ($this->has('photos')) {
                foreach ($this->photos as $photo) {
    
                    $this->photos()->detach($photo);
                }
            }
           
        } catch (Exception $e) {
            dd($e);
        }
}

Then you can simply call this where you need:

$user->detach();
$user->delete();

Upvotes: 3

Ranga Lakshitha
Ranga Lakshitha

Reputation: 306

It is better if you override the delete method for this. That way, you can incorporate DB transactions within the delete method itself. If you use the event way, you will have to cover your call of delete method with a DB transaction every time you call it.

In your User model.

public function delete()
{
    \DB::beginTransaction();

     $this
        ->photo()
        ->delete()
    ;

    $result = parent::delete();

    \DB::commit();

    return $result;
}

Upvotes: 13

Daanzel
Daanzel

Reputation: 1037

You can use this method as an alternative.

What will happen is that we take all the tables associated with the users table and delete the related data using looping

$tables = DB::select("
    SELECT
        TABLE_NAME,
        COLUMN_NAME,
        CONSTRAINT_NAME,
        REFERENCED_TABLE_NAME,
        REFERENCED_COLUMN_NAME
    FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
    WHERE REFERENCED_TABLE_NAME = 'users'
");

foreach($tables as $table){
    $table_name =  $table->TABLE_NAME;
    $column_name = $table->COLUMN_NAME;

    DB::delete("delete from $table_name where $column_name = ?", [$id]);
}

Upvotes: -1

Steve Bauman
Steve Bauman

Reputation: 8688

To elaborate on the selected answer, if your relationships also have child relationships that must be deleted, you have to retrieve all child relationship records first, then call the delete() method so their delete events are fired properly as well.

You can do this easily with higher order messages.

class User extends Eloquent
{
    /**
     * The "booting" method of the model.
     *
     * @return void
     */
    public static function boot() {
        parent::boot();

        static::deleting(function($user) {
             $user->photos()->get()->each->delete();
        });
    }
}

You can also improve performance by querying only the relationships ID column:

class User extends Eloquent
{
    /**
     * The "booting" method of the model.
     *
     * @return void
     */
    public static function boot() {
        parent::boot();

        static::deleting(function($user) {
             $user->photos()->get(['id'])->each->delete();
        });
    }
}

Upvotes: 6

Paras
Paras

Reputation: 9465

There are 3 approaches to solving this:

1. Using Eloquent Events On Model Boot (ref: https://laravel.com/docs/5.7/eloquent#events)

class User extends Eloquent
{
    public static function boot() {
        parent::boot();

        static::deleting(function($user) {
             $user->photos()->delete();
        });
    }
}

2. Using Eloquent Event Observers (ref: https://laravel.com/docs/5.7/eloquent#observers)

In your AppServiceProvider, register the observer like so:

public function boot()
{
    User::observe(UserObserver::class);
}

Next, add an Observer class like so:

class UserObserver
{
    public function deleting(User $user)
    {
         $user->photos()->delete();
    }
}

3. Using Foreign Key Constraints (ref: https://laravel.com/docs/5.7/migrations#foreign-key-constraints)

$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');

Upvotes: 36

Calin Blaga
Calin Blaga

Reputation: 1373

Relation in User model:

public function photos()
{
    return $this->hasMany('Photo');
}

Delete record and related:

$user = User::find($id);

// delete related   
$user->photos()->delete();

$user->delete();

Upvotes: 45

Chris Schmitz
Chris Schmitz

Reputation: 8257

You can actually set this up in your migrations:

$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');

Source: http://laravel.com/docs/5.1/migrations#foreign-key-constraints

You may also specify the desired action for the "on delete" and "on update" properties of the constraint:

$table->foreign('user_id')
      ->references('id')->on('users')
      ->onDelete('cascade');

Upvotes: 259

Alex
Alex

Reputation: 91

In my case it was pretty simple because my database tables are InnoDB with foreign keys with Cascade on Delete.

So in this case if your photos table contains a foreign key reference for the user than all you have to do is to delete the hotel and the cleanup will be done by the Data Base, the data base will delete all the photos records from the data base.

Upvotes: 0

akhy
akhy

Reputation: 5880

Note: This answer was written for Laravel 3. Thus might or might not works well in more recent version of Laravel.

You can delete all related photos before actually deleting the user.

<?php

class User extends Eloquent
{

    public function photos()
    {
        return $this->has_many('Photo');
    }

    public function delete()
    {
        // delete all related photos 
        $this->photos()->delete();
        // as suggested by Dirk in comment,
        // it's an uglier alternative, but faster
        // Photo::where("user_id", $this->id)->delete()

        // delete the user
        return parent::delete();
    }
}

Hope it helps.

Upvotes: 65

Darren Powers
Darren Powers

Reputation: 77

Or you can do this if you wanted, just another option:

try {
    DB::connection()->pdo->beginTransaction();

    $photos = Photo::where('user_id', '=', $user_id)->delete(); // Delete all photos for user
    $user = Geofence::where('id', '=', $user_id)->delete(); // Delete users

    DB::connection()->pdo->commit();

}catch(\Laravel\Database\Exception $e) {
    DB::connection()->pdo->rollBack();
    Log::exception($e);
}

Note if you are not using the default laravel db connection then you need to do the following:

DB::connection('connection_name')->pdo->beginTransaction();
DB::connection('connection_name')->pdo->commit();
DB::connection('connection_name')->pdo->rollBack();

Upvotes: -1

Related Questions