From 500 to 404: A Route Model Binding Debug Tale

Vincent Bergeron • August 29, 2024

Today, I had an interesting pairing session with a fellow developer at work. We were going over one of their controllers when I noticed they weren't using Laravel's Route Model Binding in the project. Since it was a relatively small and simple controller, I took the opportunity to show them how this feature works and how it can simplify the code.

The process started smoothly. We updated the route definition by changing the parameter from user_id to user and type-hinted the User model in the controller action. The best part? They already had an automated test for this controller action! The test existed ensure what happens if the user_id was associated to no existing user. So, all we had to do was adjust the test, changing the route parameter to match the new setup, and then run the tests.

To our surprise, the test failed.

Instead of getting a 404 for a non-existent user (like before), the controller threw a 500 error. This was unexpected because Laravel is supposed to return a 404 if a model can’t be found. After a bit of head-scratching and some investigation, we noticed that the controller was actually receiving an empty User model, with its attributes array completely empty.

After spending more time than we’d like to admit tracking down the issue, we finally found the culprit: the setUp method in the test class. It contained a $this->withoutMiddleware() call, hidden away near the bottom of the method, which we had initially overlooked. This line of code was the problem.

Laravel's Route Model Binding comes from the SubstituteBindings middleware. Since the middleware was being bypassed, Laravel couldn't handle the binding, and instead, it injected an empty user model.

Once we removed the withoutMiddleware() call, the tests passed successfully, and everything behaved as expected, with Laravel returning the appropriate 404 error for a missing user.

This gave me a good refresh about how Route Model Binding works, and next time I'll make sure to triple-check the setUp method!