Laravel QueryException bypassing try-catch?
Asked Answered
E

1

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();
}
Efflorescent answered 19/6, 2014 at 16:51 Comment(8)
If you're using Laravel 4.2, the way soft deletion works has changed - see here: laravel.com/docs/upgrade. But if you're not, then I'm afraid I don't know the cause.Somewhere
Let it throw an exception and it should tell you what exception it's throwing. Should just be able to copy and paste that.Triboluminescence
Nope. It's Laravel 4.1. But thanks for your help!Efflorescent
@user3158900 I'm sorry, I didn't understand. Can you be more specific?Efflorescent
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 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.Triboluminescence
It doesn't output because it's an AJAX request. I've updated the question with the Firebug response.Efflorescent
Any errors in app/storage/logs/laravel.log?Feathering
@Feathering 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.Efflorescent
F
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();
}
Feathering answered 19/6, 2014 at 17:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.