Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Catch exception from controller in middleware

I have a laravel controller that can throw an exception, and a global middleware that catches that exception. In semi pseudo code:

// App\Controllers\...
class Controller {
  function store() {
    throw new FormException; // via validation etc, but it's thrown here
  }
}

// App\Http\Middleware\...
class Middleware {
  function handle(Closure $next) {
    try {
      // Breakpoint 1
      return $next(); // $response
      // Breakpoint 2
    }
    catch (FormException $ex) {
      // Breakpoint 3
      exit('FormException caught!');
    }
  }
}

The problem is that the exception is never caught. Somwhere in the pipeline, the application catches the exception and prints a pretty error page, but it should be caught by my middleware so it can handle it properly.

  • Breakpoint 1 should trigger, and it does << good
  • Breakpoint 2 shouldn't trigger, and it doesn't << good
  • Breakpoint 3 should trigger, but it doesn't << what??

The only way I can imagine my middleware not catching it, is if it's caught somewhere deeper inside the pipeline, not further up/around, but I can't find any try/catch in other middleware, or in the pipeline execution code.

Where is this exception caught? Why?

This might not be a great pattern, but I don't care about that now. I'm more curious than anything else. Do I completely misunderstand Laravel's middleware?

My own super simple middleware test does what I expected: https://3v4l.org/Udr84 - catch and handle exception inside middleware.

Notes:

  • The $response object (return value of $next()) is the handled exception page, so it has already been handled. Where and why?
  • Handling the exception in App\Exceptions\Handler::render() works, but I want all logic to be in a middleware package, not in app code.

Relevant Laravel code:

  • Kernel::handle() starts the middleware pipeline << this has a catch-all catch(), but my catch() comes first, right?
  • Pipeline::then() starts the middleware execution
  • Pipeline::getSlice() handles and creates the $next closures
like image 312
Rudie Avatar asked Jul 30 '16 23:07

Rudie


People also ask

How do you catch exceptions in controller?

Another way to handle controller level exceptions is by overriding the OnException() method in the controller class. This method handles all your unhandled errors with error code 500. It allows you to log an exception and redirect to the specific view. It does not require to enable the <customErrors> config in web.

Which of the following middleware is used for exception handling?

Exception handler page To configure a custom error handling page for the Production environment, call UseExceptionHandler. This exception handling middleware: Catches and logs unhandled exceptions. Re-executes the request in an alternate pipeline using the path indicated.

Should I catch exception controller?

Exceptions are not a good way to do it, although possible. Validations should return boolean true or false to the controller, and then the controller returns a response to the user with an explanatory message. Don't return code 500 for validations; that code is meant for server errors, not user errors.


3 Answers

I had the same problem. When reading the thread Rudie mentioned, they give a possible solution there which worked for me:

public function handle(Request $request, Closure $next) {
  $response = $next($request);

  // 'Catch' our FormValidationException and redirect back.
  if (!empty($response->exception) && $response->exception instanceof FormValidationException) {
    return redirect()->back()->withErrors($response->exception->form->getErrors())->withInput();
  }

  return $response;
}
like image 60
Jetse Avatar answered Oct 24 '22 03:10

Jetse


Apparently this is by design:

Yes, this is the beavhiour starting from L5.2. Throwing an exception causes the response to be set as that returned from the exception handler, and then the middleware is allowed to backout from that point.

I think that's very strange. Pluggable middleware would be perfect for catching exceptions.

Two ways to still do this:

  • Proper: in App\Exceptions\Handler, which is not good enough, because a package can't touch that
  • Funky: take the original exception object from the response object:

    $response = $next($request);
    $exception = $response->exception;
    
like image 30
Rudie Avatar answered Oct 24 '22 04:10

Rudie


Looking at the source code, you need to catch both \Exception and \Throwable for your try catch to properly work in your middleware. This works on Laravel 5.8

class TryCatchMiddleware
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request $request
     * @param  \Closure $next
     * @return mixed
     */


    public function handle($request, Closure $next)
    {

        try {
           if ( somethingThatCouldThrowAnException() ) {
                $request->newVariable = true;
           }
        } catch (\Exception $e) {
            // do nothing
        } catch (\Throwable $e) {
            // do nothing
        }

        return $next($request);
    }
}
like image 40
Ray Avatar answered Oct 24 '22 03:10

Ray