Home » Php » php – Laravel: Where to throw HTTP Exceptions

php – Laravel: Where to throw HTTP Exceptions

Posted by: admin July 12, 2020 Leave a comment

Questions:

Background

Within PHP / Laravel MVC applications response codes and bodies are often dictated by the Exception that is thrown. If a HTTP exception is thrown (inheriting from Symfony\Component\HttpKernel\Exception\HttpException) the correct response codes are thrown (and in certain cases JSON responses). There are other types of exceptions that are non-http related as well that can be thrown.

Question

Where should HTTP exceptions be thrown?

  • A Only the controller
  • B Anywhere. Deep or shallow in the applications stack.

Should I be catching my exceptions in the controller and throwing HTTP versions of those exceptions? Or should I just throw a HTTP exception anywhere deep within a service class, repository or utility considering 99% of MVC framework apps are based around a HTTP request >> response lifecycle anyway?

How to&Answers:

My answer is not targeted at Laravel as I feel working with a framework mindset actually goes against your initial question.

Always throw a tailored exception and then handle the conversion within the controller. In this case wrap it in a HttpException. There are a few good reasons for this:

  • The decision on which status code and message is delegated to the implementation (in this case the integration with your framework). This means that you could drop your code in any framework and handle its errors separately.
  • You decide you need a CLI command/worker and now your HttpException throws in your service make no sense to your CLI command.

Essentially thinking about a calculator, it would throw a DivisionByZeroException. For a controller you would wrap this in a HttpException 400 BAD REQUEST and re-throw. For the CLI your command could just let the exception render on screen Division By Zero. Either way this decision is not made by your service.

Answer:

Where should HTTP exceptions be thrown?

While this is generally up to preference, the framework itself seems to have taken an opinionated stance on this and it is that you should be throwing them anywhere. In fact Laravel has a few useful helpers to make throwing exceptions with associated response codes easier:

abort(403, "Exception message"); //Will throw an HTTP exception with code 403
abort_if(true, 400, "Condition failed"); //Will throw a 400 error if the first parameter is true
abort_unless(false, 422, "Condition failed"); //Will throw a 422 error if the first parameter is false

Practical example:

 public function getById($id) { 
      $model = Model::find($id);
      //These are equivalent 
      if ($model == null) {
         abort(404, "$id not found");
      }
      abort_if($model == null, 404, "$id not found");  
      abort_unless($model != null, 404, "$id not found");
 }

This is touched upon in the Error handling section of the manual

Note that abort does raise HTTP exceptions so you can still catch them and handle them if you need to.

There seems to be a general misunderstanding regarding this question. It was my understanding that the question was where HTTP exceptions should be thrown but it’s evolving to a more generic exception handling in the context of HTTP.

First of all, if you have an HTTP exception, meaning an exception that only makes sense in the context of an HTTP request/response cycle, then you should be able to throw it where it occurs and not throw something else with the purpose of converting it when it reaches the controller, this is what the abort helpers are there to do.

However if you have an exception (any kind of exception) that should be interpreted with a specific http response code when left unhandled you have options to deal with that:

  1. Make that exception inherit from the Symfony HttpException (This might feel a bit strange that a perfectly normal exception inherits from a class that doesn’t make sense outside the request/response lifecycle).
  2. Implement the render method within your exception e.g.:

    class SpecialException extends Exception { 
       public function render() {
            return response()->view('errors.403', [], 403);
       }
    }
    
  3. Have a specific handling behaviour within your \App\Exceptions\Handler for example:

    class Handler {
           // ....
          public function render($request, $exception) {
              if ($exception instanceof SpecialException) {
                  return response()->view('errors.403', [], 403);
              }
              return parent::render()
          }
    }