Whoops Integration

Mark Kimsal bio photo By Mark Kimsal

I ran across the exception handling library “Whoops” today and the example pages look amazing. So, I decided to hook it into Metro Framework. The front page of the site gives you some sample PHP to start-up the exception handler in any framework, but I decided to go a different route and integrate it more closely into MetroPHP’s own exception handler.

Whoops Screenshot

Default Usage

The regular usage is pretty straight forward:

composer require filp/whoops
#note that it's filp not flip
$run     = new Whoops\Run;
$handler = new Whoops\Handler\PrettyPageHandler;

// Set the title of the error page:
$handler->setPageTitle("Whoops! There was a problem.");

$run->pushHandler($handler);

// Register the handler with PHP, and you're set!
$run->register();

Instantiate 2 objects, set them up, connect them, register, boom, you’re done.

Metro Integration

I wanted to integrate the exception handling output directly into Metro’s exception handling so that we don’t have too many handlers running at the same time if it can easily be avoided.

In the bootstrap.php file I pasted in the sample code from the project and started reworking the register() call. First, I looked at what it was doing and saw that it was just registering normal PHP error handlers, so I figured I could just call the whatever the runner calls inside its own error handler from mine. (Luckily, all the methods are public)

$run     = new Whoops\Run;
$handler = new Whoops\Handler\PrettyPageHandler;

// Set the title of the error page:
$handler->setPageTitle("Whoops! There was a problem.");

$run->pushHandler($handler);

_iCanHandle('exception', function() use($run) {
    $run->handleException( _get('last_exception') );
});

So what is going on in this code? We cannot directly use the Run object’s handleException function because it requires an exception object and it is typed hinted!

public function handleException(Exception $exception) 

_iCanHandle('exception', array($run, 'handleException')); // Won't work!

_iCanHandle('exception', array($run, 'handleException'), _get('last_exception')); // Also won't work!

The last one won’t work because the _get() call is evaluated right in the bootstrap file and there is no exception yet.

This is where the closure comes in. The _iCanHandle function call uses a closure to wrap up and encapsulate the integration point between the framework and the library. The framework is used to passing around things like Request and Response objects. So, when it comes time to hand off an Exception, we need a little glue code. This glue code saves us from a mountain of class files if we were to do a more academic OOP approach.

Metro Alternative

Alternatively, the glue code can be wrapped up in a file and called in a more traditional manner.

//in bootstrap.php
_iCanHandle('exception', 'main/whoopsexception.php');

//in src/main/whoopsexception.php
class Main_Whoopsexception {

	public function exception() {
		$run     = new Whoops\Run;
		$handler = new Whoops\Handler\PrettyPageHandler;

		// Set the title of the error page:
		$handler->setPageTitle("Whoops! There was a problem.");

		$run->pushHandler($handler);
		$run->handleException( _get('last_exception') );
	}
}

In this approach, we save ourselves the memory overhead of compiling and storing a closure when all we need is a simple string to indicate which file holds the glue code. Since everything in MetroPHP is lazy loaded until it is actually needed, we won’t load the Whoops library or the Main_Whoopsexception shim library until an Exception has occured.

Additionaly, we keep the etc/bootstrap.php file clean from too much object code and keep it looking like a config file ;)

Finishing Touches

Lastly, we don’t want to display exception content - no matter how nice it looks - to end users. We can wrap up the whoops integration library or closure in an environment check

//in bootstrap.php
if (_get('env') == 'dev') {
    _iCanHandle('exception', 'main/whoopsexception.php');
}