Thursday, February 19, 2015

App::error, Accept:application/json, and app.debug = false

Application crashed? Client only accepts application/json? Unless your Laravel 4 application is in debug mode, you're out of luck: the client receives text/html!



I've been working with a mobile app developer recently to flush out a substantial API. Our agreed API contract stipulates that the app will always receive JSON. This was working fine until tested in production mode: suddenly, the app started getting HTML responses on expected bad API calls. Huh, what?

Turns out, Laravel 4 only returns JSON when the application is in debug mode. This surprised me, as Laravel is usually quite good with consistency in different application states. Here is the code to spit out JSON when the application crashes and is not in debug mode:

\App::error(function(Exception $exception, $code) {
    \Log::error($exception);

    $want = (\Request::ajax() || \Request::wantsJson() || \App::runningInConsole());
    if ($want && ! \Config::get('app.debug')) {
        return Response::json(
            [ 'status' => 'error', 'message' => 'Whoops, something went wrong' ],
            $code
        );
    }
});


Dump this in app/start/global.php and you're done...

Hold on

In my case, a package implements the API. I really want the package to register this error handler. Naturally, that means calling this code in the package service provider, right? Nope. The service provider gets booted regardless of whether my API route is called, so the service provider route again pollutes the application with my package needs.

Well, then, how about in the package routes file? No again, that's loaded by the package service provider during boot.

Well, what about a route group? No, again. Route groups are loaded regardless of whether the route is ever called. Is there an end to this madness? Yes! Route filters!

I want this error catching behavior only when one of my package routes is called. To get that, I can group my routes together with a before filter:

\Route::filter('json_error', function () {
    \App::error(/* from above */);
});
\Route::group(['before' => 'json_error', 'prefix' => 'api'], function () {
    // only these routes will have the custom App::error behavior
});

Best Practice for registering App::error

Be mindful of how broad reaching you want your error handler to be. Choose the most specific location to register the handler to accomplish the goal. If the error handler applies only to a subset of routes, use a before filter on those routes. Otherwise, register it in the package service provider or app/start/global.php.

0 comments:

Post a Comment

Share your thoughts!