7

I am using Laravel 4, and the Eloquent ORM. In my system, when someone deletes a record, it has to check if it has any associated records. If it doesn't, then it may be deleted permanently. But if it does, just perform a softDeletion.

The way that this situation is being handled is: try to forceDelete, and if it throws an Exception because of the referential integrity, catch it and softDelete. I know this looks gimmicky, but it was made by another developer and I'd rather not mess with his code.

What he did was to delete, then if it threw an Exception, just set a flag to "inactivate" the record. It did work well. However, when I took over I implemented softDeleting to make things less gimmicky.

Now, when it tries to forceDelete,it throws the QueryException but doesn't fall into the catch block. I've tried changing Exception to \Exception, QueryException, Illuminate\Database\QueryException, but no success. Any ideas?

To illustrate it better:

It was like this:

try
{
    $contact->delete();
}
catch(Exception $ex)
{
    $contact->status = 0;
    $contact->save();
    //this works
}

And now it is like this:

protected $softDelete = true;

....

try
{
    $contact->forceDelete();
}
catch(Exception $ex)
{
    $contact->delete();
    //this doesn't work
}

Firebug response:

{"error":{"type":"Illuminate\\Database\\QueryException","message":"SQLSTATE[23000]: Integrity constraint violation: 1451 
Cannot delete or update a parent row: a foreign key constraint fails (`tst_db\/contact_company`, CONSTRAINT `fk_contact_company_contacts_id` 
FOREIGN KEY (`contact_id`) REFERENCES `contacts` (`id`) ON DELETE NO ACTION ON UPDATE CASCADE) 
(SQL: delete from `contacts` where `id` = 28)","file":"\/Applications\/XAMPP\/xamppfiles\/htdocs\/application\/vendor\/laravel\/framework\/src\/Illuminate\/Database\/Connection.php","line":555}}

This is the forceDelete() function from Illuminate/Database/Eloquent/Builder.php:

    public function forceDelete()
{
    return $this->query->delete();
}
Gustavo Silva
  • 315
  • 2
  • 4
  • 11
  • If you're using Laravel 4.2, the way soft deletion works has changed - see here: http://laravel.com/docs/upgrade. But if you're not, then I'm afraid I don't know the cause. – Joel Hinz Jun 19 '14 at 16:57
  • Let it throw an exception and it should tell you what exception it's throwing. Should just be able to copy and paste that. – user1669496 Jun 19 '14 at 17:02
  • Nope. It's Laravel 4.1. But thanks for your help! – Gustavo Silva Jun 19 '14 at 17:03
  • @user3158900 I'm sorry, I didn't understand. Can you be more specific? – Gustavo Silva Jun 19 '14 at 17:03
  • Run it and then it should output the error page in the browser and the error page should contain the exception that was thrown. You should just be able to copy and paste that into your code where `Exception` is. For example on this page http://filp.github.io/whoops/demo/ it's showing `RuntimeException` so you'd catch it with `catch(RuntimeException)` in your try/catch block. Can you also post your `forceDelete()` function as well, maybe there is an underlying issue which is throwing these exceptions. If the query is bad, this won't matter anyway. – user1669496 Jun 19 '14 at 17:06
  • It doesn't output because it's an AJAX request. I've updated the question with the Firebug response. – Gustavo Silva Jun 19 '14 at 17:09
  • Any errors in `app/storage/logs/laravel.log`? – Unnawut Jun 19 '14 at 17:13
  • @Unnawut yes. The same as my Firebug response, but with the stack trace. Basically exception was throw on Connection.php, which was called by Builder.php, which was called by Model.php, which was called by my forceDelete() instruction. – Gustavo Silva Jun 19 '14 at 17:20

1 Answers1

4

Your $contact->forceDelete(); will call the method in Illuminate\Database\Eloquent\Model which has the following code:

public function forceDelete()
{
    $softDelete = $this->softDelete;

    // We will temporarily disable false delete to allow us to perform the real
    // delete operation against the model. We will then restore the deleting
    // state to what this was prior to this given hard deleting operation.
    $this->softDelete = false;

    $this->delete();

    $this->softDelete = $softDelete;
}

Now what happen is your code will error on $this->delete(); above and throw an exception.

So it reaches your catch, and so you call $contact->delete(); once more. So it gets another QueryException, without $this->softDelete ever set back to true.

What you need to do is set soft delete back and try delete it again:

try
{
    $contact->forceDelete();
}
catch(Exception $ex)
{
    $contact->softDelete = true;
    $contact->delete();
}
Unnawut
  • 7,500
  • 1
  • 26
  • 33