Laravel 5.5 why is exception not thrown by foreign key violation from delete method?
Asked Answered
I

0

7

Update 3: This problem from 9 months ago persists with Laravel 5.8 and Postgresql 11 on Windows 10 in a Laragon environment and Apache 2.4 on an Ubuntu machine. Does the Eloquent Model instance delete() method simply ignore foreign key constraints? It's baffling:

I have two tables in a Postgresql 9.3 database, companies and sites. There is a one-to-many relationship between them with companies on the one side. There is a foreign key constraint that prevents the deletion of a company if sites have been assigned to it. If I attempt to delete the company with the ID 'KSL' using an SQL query directly on the database, I get the expected error:

ERROR: update or delete on table "companies" violates foreign key constraint "sites_company_id_fkey" on table "sites" DETAIL: Key (company_id)=(KSL) is still referenced from table "sites".

I have defined an artisan command whose handler method has a simple try/catch block:

public function handle()
{
    $company = Company::find('KSL');
    try{
        $company->delete();
    }catch(\PDOException $e){
        $this->info($e->getMessage());        
    }
}

When I run the command from the console, I get the expected error message:

SQLSTATE[23503]: Foreign key violation: 7 ERROR: update or delete on table "companies" violates foreign key constraint "sites_company_id_fkey" on table "sites" DETAIL: Key (company_id)=(KSL) is still referenced from table "sites". (SQL: delete from "companies" where "company_id" = KSL)

reflecting the native error message generated by Postgresql. However, when I call the delete method from a Controller using an Ajax call using a similar try/catch block, the exception is not caught and the call fails with no details of the error. I simplified the controller method to make it the same as the console handler:

    public function deleteModel(Request $request) {
        try {
            $id = 'KSL';
            $company = Company::find($id);
            $result = $company->delete();
            return 'success';
        } catch (\PDOException $e) {
            return $e->getMessage();
        }
     }

Normally I would get the value of $id from the request argument. If I use a get request with a RESTful URL in the browser, I get a "The connection was reset" message in Firefox and a similar message in Chrome. I have referred back to an old question of mine which I thought had the solution, but running composer dump-autoload had no effect. I have cleared the cache, re-installed Laravel 5.5, updated my installation, and called composer dump-autoload again several times but the absence of any exception or logged error message gives me no clue. Debug is set to true for this development app.

I passed a handler to the PHP native function register_shutdown_function like this in the autoload.php file in the bootstrap folder:

register_shutdown_function(function () {
    $error = error_get_last();
    file_put_contents(__DIR__.'/../storage/crash.log', var_export($error, true));
});

Only the word 'NULL' appears in the crash.log. I checked the Apache 2.4 error.log file and the specific error log for this Laravel app but there no relevant details recorded there.

Here is the Exception Handler:

<?php

namespace App\Exceptions;

use Exception;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;

class Handler extends ExceptionHandler
{
    /**
     * A list of the exception types that are not reported.
     *
     * @var array
     */
    protected $dontReport = [
        //
    ];

    /**
     * A list of the inputs that are never flashed for validation exceptions.
     *
     * @var array
     */
    protected $dontFlash = [
        'password',
        'password_confirmation',
    ];

    /**
     * Report or log an exception.
     *
     * This is a great spot to send exceptions to Sentry, Bugsnag, etc.
     *
     * @param  \Exception  $exception
     * @return void
     */
    public function report(Exception $exception)
    {
        parent::report($exception);
    }

    /**
     * Render an exception into an HTTP response.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Exception  $exception
     * @return \Illuminate\Http\Response
     */
    public function render($request, Exception $exception)
    {
        return parent::render($request, $exception);
    }
}

UPDATE 1: I got a clue from this question: Stack size for Apache under Windows. I quickly tested my online applications (all running on Linux machines) and there is no problem. The exception is thrown correctly and a nice clear message is displayed for the user. My local environment is Windows and it looks like Apache suffers from this connection reset error more than in a Linux environment. I've increased the stack size in Apache as suggested by the answer to that question but it still doesn't work. I still get a connection reset error. I've re-installed Apache with the latest binaries from Apache lounge I'm running PHP 7.3. Can anyone shed some light on this?

UPDATE 2: An answer from Lucas to this question encouraged me to change server. When I ran php artisan serve from the console and then call the ParentTable->delete() method, I got the expected exception with no crashing. There is clearly something wrong with my Apache configuration. Unfortunately the accepted answer to that question doesn't solve my problem. I increased the stack size but the problem persists.

Intelligence answered 10/1, 2019 at 14:6 Comment(15)
Foreign key error is thrown because a sites_company_id_fkey value cannot exist on it's own without having a parent's reference. You may wish to have onDeleteCascade() or a softDeletes() on tables.Wingspread
@vivek_23 I know that. I hoped that the detail in my question would make it clear that I understand relational databases and foreign key constraints. I don't want any user to delete a company if sites have already been assigned to it. I need the error to be thrown as an exception so that I can give the user the necessary feedback about what went wrong.Intelligence
If all you see in crash.log is NULL, then that tells me error_get_last() returned NULL, which also tells me there was no last error. That suggests to me the error is handled, but is not being reported in a helpful way. Please post your controller code.Denysedenzil
I added a simplified version of the controller deleteModel method in the question. I've upgraded to Laravel 5.7 and PHP 7.3 but I get exactly the same problem.Intelligence
@Intelligence I would have rather got a count of no. of sites for a company and returned a message accordingly than expecting it to get crashed and caught in catch to return a error message.Wingspread
When I changed from the instance method delete to the static method destroy, I got the correct exception message displaying in the browser but the second and subsequent times it went back to "The connection was reset"Intelligence
@vivek_23 I understand we have different opinions on how to handle this, but the fact is that if Laravel crashes on this kind of predictable error manipulating data in a database, it's broken and needs to be fixed. The artisan command behaves as expected, throwing and displaying the predictable exception. The controller doesn't in the browser environment. That's a problem.Intelligence
@Intelligence Are you sure there is going to be a PDOException ? Can you keep superclass Exception for now and check?Wingspread
I’ve checked \Throwable, \Exception, Illuminate\Database\QueryException to no avail. The point is the artisan command throws the expected exception; why doesn’t the controller in the browser environment?Intelligence
Quite weird. I just gave it a try on my machine. A QueryException was thrown correctly. Could you post the contents of app\Exceptions\Handler.php?Lananna
Remove try/catch then see the error log, if there is still nothing, consider whether the method should be called from a CLI.Arbela
@Lananna I've added app\Exceptions\Handler.php but it's just the file supplied by a fresh install of Laravel 5.5. I've never edited that file. This is on a Windows machine. I wonder if there's something wrong with my Apache 2.4 configuration.Intelligence
Indeed. Quite strange. Maybe xdebug could help in debugging this a bit better.Lananna
Did you checked with :: catch (\Exception $e) ?Tamishatamma
I had a number of issues with Windows Server & Apache. Updating the system finally did help.Execrate

© 2022 - 2024 — McMap. All rights reserved.