skip to Main Content

I’m trying to modify my AppExceptionsHandler to pass the request (and therefore current URL) through to all exceptions. For this reason I need the lowest-level exception class I can get hold of to type-hint to the ->renderable() method.

Laravel/Symfony’s HttpException works but only for HTTP errors, leaving out all non-HTTP exceptions. PHP’s Exception class works when using getCode() instead of getStatusCode(), but always returns a "0" for both HTTP errors and exceptions. Is there another low-level exception class that will work for my purposes, or otherwise any other way to accomplish what I’m trying to do here?

public function register()
{

$this->renderable(function (Exception $exception, $request) {

    $url = $request->fullUrl();
    $status = $exception->getCode();

    Log::warning("Error $status when trying to visit $url. Received the following message: " . $exception->getMessage()); 

    return response()->view("errors.$status", [
        "exception" => $exception
        ],
    $status
    );    
    });
   
    }

}

For what it’s worth, I’m using the following web routes to trigger exceptions and HTTP errors for testing:

if (app()->environment('local')) {
    Route::get("/exception", function (){
        throw new JsonException; // chosen because it's one of the few Laravel exceptions 
                                 // that doesn't seem to automatically resolve to a HTTP error
    });
}

if (app()->environment('local')) {
    Route::get("/fail/{status}", function ($status){
        abort($status);
    });
}

3

Answers


  1. Chosen as BEST ANSWER

    I finally managed to figure this out in the end. It's probably not the cleanest solution, but it works perfectly for my needs.

    It works by inspecting each instance of the Exception class and using PHP's instanceof() to check whether it's a HTTP exception or not. If it is, it gets logged with the request URL and returns a view with a status code. If it's a generic non-HTTP exception, it gets logged with the request URL and returns another view with no status code (or you can keep the default exception behaviour by removing the return block, which renders a blank screen in production).

    public function register()
    {
    
    $this->renderable(function (Exception $exception, $request) {
    
      $url = $request->fullUrl();
    
      if ($exception instanceof HttpException) {
      
      $status = $exception->getStatusCode();
    
      Log::warning("Error $status occurred when trying to visit $url. Received the following message: " . $exception->getMessage()); 
    
      return response()->view("errors.error", [
      "exception" => $exception,
      "status" => $status
      ],
      $status
      );
    
    } else {
    
      $status = $exception->getCode();
    
      Log::warning("Exception $status occurred when trying to visit $url. Received the following message: " . $exception->getMessage()); 
    
      return response()->view("errors.exception", [
      "exception" => $exception,
      "status" => $status
    
      ]);
      }
    });
    
    // Optionally suppress all Laravel's default logging for exceptions, so only your own logs go to the logfile 
    $this->reportable(function (Exception $e) {
    })->stop();
    
    }
    

  2. You can throw your JsonException and abort like so with a given code and the handler should grab it from getCode like so

    // in your controller
    throw new JsonException('Something went wrong', 500);
    // or
    abort(500, 'Something went wrong')
    
    // in your handler
    $status = $e->getCode(); // 500
    $message = $e->getMessage(); // "Something went wrong" 
    

    That said it’s better to keep them as semantically separate as possible in my opinion, and let the handler do the handling depending on what it receives.

    Login or Signup to reply.
  3. As requested, this is what I have in my Handler. I use some custom logging, and I want to make sure I grab the right code when it’s an HTTP error.

    public function report(Throwable $e)
    {
        $code = match (get_class($e)) {
            'SymfonyComponentHttpKernelExceptionNotFoundHttpException' => 404,
            HttpException::class => $e->getStatusCode(),
            default => 'No Code',
        };
        // more stuff here 
     }
    

    You can use $e->getCode() for your default as well

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search