Reputation: 12995
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
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
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
Reputation: 389
$table->foreignId('user_id')->constrained('user')->cascadeOnDelete();
or
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
Upvotes: 0
Reputation: 578
Add delete function on model that you want to delete
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
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
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
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
Reputation: 4281
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.
$table->unsignedBigInterer('user_id');
$table->foreign('user_id')
->references('id')
->on('users')
->onDelete('cascade');
$table->foreignId('user_id')
->constrained()
->onDelete('cascade');
Upvotes: 9
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
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
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
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
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
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
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
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
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
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