Laravel HasMany: Restrict Parent Delete to Avoid QueryException

If you have a hasMany relationship and want to restrict/prevent deleting the parent record if it has at least one child record, this tutorial will show you two ways: check if children exist or try-catch if Exception happens.

Let's imagine we have User -> hasMany -> Task, and then this Controller that uses Route Model Binding:

UserController.php:

public function destroy(User $user) {
$user->delete();
 
return redirect('users.index')
->with('message', 'User deleted successfully');
}

Now, what if our User has any Tasks in the Database? You would have this error:

Illuminate\Database\QueryException SQLSTATE[23000]:
Integrity constraint violation:
1451 Cannot delete or update a parent row:
a foreign key constraint fails
(`project`.`tasks`, CONSTRAINT `tasks_user_id_foreign`
FOREIGN KEY (`user_id`) REFERENCES `users` (`id`))
(Connection: mysql, SQL: delete from `users` where `id` = 1).

This error happens on the database level, so we need to either prevent that query from being run in the first place or catch this error when it occurs.


Solution 1. Check to Prevent Query From Execution

You can add a simple if-statement before deleting.

UserController.php:

public function destroy(User $user) {
if ($user->tasks()->count() > 0) {
return back()
->with('error', 'Cannot delete users that have tasks');
 
// Then, just show the session('error') in the Blade
}
$user->delete();
 
return redirect('users.index')
->with('message', 'User deleted successfully');
}

Solution 2. Try-catch with QueryException

As you can see, the error above is described with this Exception class: Illuminate\Database\QueryException. So we could catch precisely that:

UserController.php:

public function destroy(User $user) {
try {
$user->delete();
} catch (Illuminate\Database\QueryException $exception) {
return back()
->with('error', 'Cannot delete users that have tasks');
}
 
return redirect('users.index')
->with('message', 'User deleted successfully');
}

In this case, we assume that foreign key constraint is the only reason that could trigger the SQL error. In reality, that's not always true. There may be other errors, including that your whole MySQL server may be down. So, I wouldn't choose this try-catch method, personally.

Notice: If you want to learn more about try-catch and Exceptions in Laravel, you can take our course Handling Exceptions and Errors in Laravel.


Showing Error in Blade

In both cases, we redirect back to the previous page, so we must show the error in its Blade. An example code could be this:

@if (session('error'))
<div class="alert alert-danger">
{{ session('error') }}
</div>
@endif

Also alternatively, if you want to allow deleting the parent record and cascade-delete children records with it, here's a tutorial about it.

No comments or questions yet...

Like our articles?

Become a Premium Member for $129/year or $29/month
What else you will get:
  • 59 courses (1057 lessons, total 42 h 44 min)
  • 78 long-form tutorials (one new every week)
  • access to project repositories
  • access to private Discord

Recent Premium Tutorials