Automatically deleting related rows in Laravel (Eloquent ORM)

PhpLaravelEloquentEloquent Relationship

Php Problem Overview


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.

Php Solutions


Solution 1 - Php

I believe this is a perfect use-case for Eloquent events (http://laravel.com/docs/eloquent#model-events). You can use the "deleting" event to do the cleanup:

class User extends Eloquent
{
    public function photos()
    {
        return $this->has_many('Photo');
    }
    
    // this is a recommended way to declare event handlers
    public static function boot() {
        parent::boot();

        static::deleting(function($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..

Solution 2 - Php

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');

Solution 3 - Php

> 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.

Solution 4 - Php

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();

Solution 5 - Php

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');

Solution 6 - Php

As of Laravel 5.2, the documentation states that these kinds of event handlers should 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 even suppose to move them to separate classes instead of closures for better application structure.

Solution 7 - Php

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;
}

Solution 8 - Php

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();
        });
    }
}

Solution 9 - Php

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');

Solution 10 - Php

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();

Solution 11 - Php

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...

Solution 12 - Php

  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');
}

Solution 13 - Php

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.

Solution 14 - Php

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',
]);

Solution 15 - Php

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();

Solution 16 - Php

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.

Solution 17 - Php

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

or

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

Solution 18 - Php

yeah, but as @supersan stated upper in a comment, if you delete() on a QueryBuilder, the model event will not be fired, because we are not loading the model itself, then calling delete() on that model.

The events are fired only if we use the delete function on a Model Instance.

So, this beeing said:

if user->hasMany(post)
and if post->hasMany(tags)

in order to delete the post tags when deleting the user, we would have to iterate over $user->posts and calling $post->delete()

foreach($user->posts as $post) { $post->delete(); } -> this will fire the deleting event on Post

VS

$user->posts()->delete() -> this will not fire the deleting event on post because we do not actually load the Post Model (we only run a SQL like: DELETE * from posts where user_id = $user->id and thus, the Post model is not even loaded)

Solution 19 - Php

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]);
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMartti LaineView Question on Stackoverflow
Solution 1 - PhpivanhoeView Answer on Stackoverflow
Solution 2 - PhpChris SchmitzView Answer on Stackoverflow
Solution 3 - PhpakhyView Answer on Stackoverflow
Solution 4 - PhpCalin BlagaView Answer on Stackoverflow
Solution 5 - PhpParasView Answer on Stackoverflow
Solution 6 - PhpAttila FulopView Answer on Stackoverflow
Solution 7 - PhpRanga LakshithaView Answer on Stackoverflow
Solution 8 - PhpSteve BaumanView Answer on Stackoverflow
Solution 9 - PhpBABAK ASHRAFIView Answer on Stackoverflow
Solution 10 - PhpCarlos A. CarneiroView Answer on Stackoverflow
Solution 11 - PhpAnasSafiView Answer on Stackoverflow
Solution 12 - PhpCyber5h13ldView Answer on Stackoverflow
Solution 13 - PhpAlexView Answer on Stackoverflow
Solution 14 - PhpPri NceView Answer on Stackoverflow
Solution 15 - PhpDarren PowersView Answer on Stackoverflow
Solution 16 - PhpLeena PatelView Answer on Stackoverflow
Solution 17 - PhpRajib Bin AlamView Answer on Stackoverflow
Solution 18 - PhprechimView Answer on Stackoverflow
Solution 19 - PhpDaanzelView Answer on Stackoverflow