Laravel: Fixing “Route not found”
There is more to just a Route::get() call
For some folks in Laravel, registering a route during the application execution is not necessary, let alone useless. The recommended way to register all the application routes is by declaring them inside the
routes/api.php files, as the documentation says.
Sometimes, you may need to create one “on demand”. For example, I tried to register a Route inside a test method to check if my Laragear MailLogin package worked correctly to send a temporary signed URL.
To my surprise, the test failed not because something in the code was wrong, but because the named route I registered couldn’t be found. It was something like this, oversimplified for illustration reasons:
The above code failed on the assertion line. The Router couldn’t find the route I just created by its name, even if I registered just a line away.
After a quick debug tour by looking into the Route Collection through the
Route::getRoutes() method confirmed it did exist, and was registered successfully, with its name.
Something was wrong. Was I wrong? Was the test wrong? Was Laravel wrong? As with all murder mysteries, there is always an explanation.
The lookup tables
Tracking back the code around routing, I found that just after the Router Service Provider receives all your pre-registered routes through their respective files, it will fire two methods that you may never heard about just now:
The first, as the name implies, will create an array of named routes pointing to the real Route instance. The second does the same, but using the (controller) action of each Route.
The purpose for these “lookup tables” is to allow the application to retrieve a Route by their name, or the action it points to. An array key lookup is miles faster than iterating through the whole Route Collection each time the app needs to find a route.
Why these exists in the first place? This is the important part. The registration is triggered only through one of the route methods, not before or after. Adding the name or action after one of these methods is called won’t register them in the lookup tables because the check was already done, so Laravel is forced to inspect the whole list to ensure they are added correctly for the convenience of the developer.
Fixing the problem of a Route not found
You may think the direct fix is relatively costly, and you’re right.
refreshActionLookups() will iterate through the whole list of Routes to compile their respective lookup tables. Calling them each time we want to register a Route is counterproductive and makes our application slower the more routes it has.
Instead, we can use a very simple trick to register a route on-demand: create the Route starting with the name and/or action, and just then the rest of the information about the Route itself.
This is not mandatory for the route files. We only need this to register a Route while the application is already booted, like during a Request or inside a test method.
In other words, adding the name and action in the Route instance before registering will allow Laravel to add them to the lookup tables immediately. Since the name and action will exist in the Route instance before the registration occurs, there is no need to invoke a lookup table refresh.
Back to the test for Laragear MailLogin, I can safely expect the name route to be found, since I can start with
name() and then use
Since all tests passed, I published the package as the faster way to handle Mail Login in Laravel.