Laravel QueryException bypassing try-catch?

Gustavo Silva picture Gustavo Silva · Jun 19, 2014 · Viewed 9.2k times · Source

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

Answer

Unnawut picture Unnawut · Jun 19, 2014

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