Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

Riari's avatar

Any way of overriding exception handling in a package? (Laravel 5)

In one of my Laravel 5 packages, I'm building a REST API using resource controllers. Responses are returned in JSON format and I have model binding set up for the resources involved, i.e.:

Route::model('category', 'Riari\Forum\Models\Category');
Route::model('thread', 'Riari\Forum\Models\Thread');
Route::model('post', 'Riari\Forum\Models\Post');

That's where I have a fundamental issue: if the user/consumer passes an invalid ID in the URL when requesting from the API, due to the model binding, a ModelNotFoundException is thrown and Laravel's exception handler kicks in, returning a 404 view response instead of a JSON one.

I want to override that, but there doesn't seem to be a way of registering an exception handler in a service provider (at least in the context of a package, without affecting other routes). What's a good way around this?

A few options I can think of are:

  1. Change the API route definitions to use different parameter names that are not picked up by the model binding, and therefore receive the IDs directly in the controllers. Not ideal because it means the routes are not as tidy and consistent with the non-API routes.
  2. Define all the API routes explicitly and pass in anonymous functions to wrap the controller calls in each one. This would enable me to keep the route components as they are, but at the cost of having a more complex routes.php and logic in it that I'd rather avoid.

Neither seem ideal. Any ideas?

(Also posted on /r/laravel)

0 likes
6 replies
jasonfrye's avatar

You could use a try catch block to intercept the ModelNotFound exception in the controller.

try {
    //your code
} catch(ModelNotFoundException $e){
    //your code when id is not found
}
1 like
Riari's avatar

Both good suggestions and it somehow never occurred to me to catch the exception. I didn't know you could use closures with model binding either; not sure how I missed that in the docs. I will try that first.

Thanks!

bimalshah72's avatar

@Riari @jasonfrye

Another great approach to handle exception is I hope you must love this

App\Exceptions\Handler.php go to

public function render($request, Exception $e)
{
    return parent::render($request, $e);
}

and update it

public function render($request, Exception $e)
{
    if($e instanceof ModelNotFoundException)
    {
        if($request->ajax())
        {
            return response()->json([
                'message' => 'Record not found' //any json here
            ],404)
        }
    }   
    return parent::render($request, $e);
}
2 likes
jasonfrye's avatar

Great answer, @bimalshah72! For some reason, I've missed the route model binding before now. Just read through the docs on that. Such an awesome feature.

Riari's avatar

Because I need this to be self-contained in a package, in the end I took the model binding & closures approach.

Just in case it comes in handy for anyone else, here's what I did:

  1. Defined my API routes in a group with a custom 'name' parameter, like so:
Route::group(['name' => 'api' ... (etc)
  1. Extended the Route facade to add an isAPI() method:
<?php namespace Riari\Forum\Support\Facades;

use Illuminate\Support\Facades\Route as BaseRoute;

class Route extends BaseRoute
{
    public static function isAPI()
    {
        $action = self::current()->getAction();
        return (isset($action['name']) && $action['name'] == 'api');
    }
}
  1. Defined the model binding:
Route::bind('category', function ($id)
{
    return bind_forum_parameter(new \Riari\Forum\Models\Category, $id);
});
Route::bind('thread', function ($id)
{
    return bind_forum_parameter(new \Riari\Forum\Models\Thread, $id);
});
Route::bind('post', function ($id)
{
    return bind_forum_parameter(new \Riari\Forum\Models\Post, $id);
});

function bind_forum_parameter($model, $id)
{
    if (\Riari\Forum\Support\Facades\Route::isAPI()) {
        return $model::find($id);
    }
    return $model::findOrFail($id);
}

And finally...

  1. In the API controller methods, I check the parameter like so:
if (!$model->exists) {
    return $this->notFoundResponse();
}

(Where notFoundResponse() is a custom method that returns a JsonResponse with an HTTP 404 status code, error message and error code)

Thanks for the help :)

Please or to participate in this conversation.