Laravel 4 Error Handling

Laravel uses Exceptions for error handling. This includes both HTTP error codes and application (run-time) errors.

You can read the docs about generating a 404 response and other error responses. It shows that we can use these methods to generate errors:

// Abort, with any HTTP status code
App::abort(404);

// Abort with message
App::abort(401, 'You are not authorized.');

// Handle specific Exceptions
App::error(function(Exception $exception)
{
    Log::error($exception);
});

I wanted to dig in and see just how Laravel 4 accomplishes the above.

I started by seeing that the Application class registers Illuminate\Exception\ExceptionServiceProvider, which in turn uses the Illuminate\Exception\Handler class to handle thrown Exceptions.

Here's the meat of the Handler class::

 /**
 * Handle the given exception.
 *
 * @param  Exception  $exception
 * @param  bool  $fromConsole
 * @return void
 */
public function handle($exception, $fromConsole = false)
{
	foreach ($this->handlers as $handler)
	{
		// If this exception handler does not handle the given exception, we will
		// just go the next one. A Handler may type-hint the exception that it
		// will handle, allowing for more granularity on the error handling.
		if ( ! $this->handlesException($handler, $exception))
		{
			continue;
		}

		if ($exception instanceof HttpExceptionInterface)
		{
			$code = $exception->getStatusCode();
		}

		// If the exception doesn't implement the HttpExceptionInterface we will
		// just use the generic 500 error code for a server side error. If it
		// implements the Http interface we'll grab the error code from it.
		else
		{
			$code = 500;
		}

		$response = $handler($exception, $code, $fromConsole);

		// If the handler returns a "non-null" response, we will return it so it
		// will get sent back to the browsers. Once a handler returns a valid
		// response we will cease iterating and calling the other handlers.
		if ( ! is_null($response))
		{
			return $response;
		}
	}
}

So, what's going on here?

The handle method iterates through every registered Exception handler. Here's what it does:

  1. Determine if a registered handler handles the give Exception. It uses some inflection magic to determine if the given handler is meant to handle the Exception. If it does not, it continues on to the next registered handler.
  2. Determine if the Exception implements HttpExceptionInterface. If the Exception implements HttpExceptionInterface (part of Symfony's HttpKernel), Laravel treats the Exception as an HTTP error and returns the status code set in the Exception.
  3. Determine if the Exception does NOT implement HttpExceptionInterface. If the Exception does not implement HttpExceptionInterface, the HTTP status code defaults to 500.
  4. Pass relevant information to that Exception handler. The Exception, the status code, and a boolean if its "from console" are sent to the registered Exception handler.
  5. Lastly, if the error handler return's a non-null response, it outputs that to the browser and stops itirating through handlers. The first Exception handler to output non-null data stops the Exception handling process.

What can I do with this?

This lets you do some interesting things.

  1. The docs mention throwing the 404 error Exception, but you can actually throw any of Symfony's Symfony\Component\HttpKernel\Exception classes and get the appropriate HTTP response sent back to the browser.
  2. You can extend Symfony's HttpException, or implement HttpExceptionInterface for yourself and throw those. For instance, if you're a little tea pot.
  3. You can use App::abort(404), which throws an HttpException and uses any HTTP status given (404 in this example).
  4. Most interestingly, you can register your own Exception handlers.

Register your own Exception handler:

Let's start with an example.

class FideloperException extends Exception {}

App::error(function(FideloperException $e, $code, $fromConsole)
{
	if ( $fromConsole )
	{
		return 'Error '.$code.': '.$e->getMessage()."\n";
	}

	return '<h1>Error '.$code.'</h1><p>'.$e->getMessage().'</p>';

});


// Later, somewhere in your app

throw new FideloperException("Oh, man, don't do that. Not on the rug, man.");

As shown above, registering an Exception handler is done via App::error() (a facade for $app['exception'], the Handler class) by passing a Closure to the error() method. My Closer type-hints FideloperException, telling the Handler that it should only handle Exceptions which are an instance of FideloperException. The Handler is able to correctly determine the type-hinting thanks to the previously mentioned inflection magic. Laravel is self-aware, like Skynet.

My handler above happens to return some output which is CLI-friendly (if run in console), otherwise it returns some HTML. Because it returns non-null data, other Exception handlers are not run.

If I wanted to, I could log this FideloperException, not return anything, and let Laravel's default Exception handler do its thing as well.

App::error(function(FideloperException $e, $code, $fromConsole)
{
	// Will log this, and then pass the Exception to the next
	// registered Exception handler
	Log::info("That rug really tied the room together, did it not?");
});