Extending default Laravel 5 Router

While working with Laravel framework sometimes it becomes necessary to extend it’s default Router. It Laravel 4 this process was pretty simple and was described in the official manual. But in Laravel 5 this process became more complicated and has some pitfalls.

Router

First part is rather easy: we have to implement our router class and a Service. First of all lets create router:

<?php namespace App\Services;

class Router extends \Illuminate\Routing\Router
{
  /**
   * Dummy method.
   *
   * @param $uri
   */
  public function hello($uri)
  {
    $this->get($uri, function()
    {
      return 'Hello!';
    });
  }
}

Here we created our custom router that extends the basic \Illuminate\Routing\Router and has a dummy method hello that just prints ‘Hello!’ whatever route we want.

Service Provider

Second we have to register our router to switch it with the default one. For this purpose we don’t have to create new service provider, we already have already created RouterServiceProvider. So lets use it add create a register method.

<?php
/**
 * Register the router instance.
 *
 * @return void
 */
protected function register()
{
  $this->app['router'] = $this->app->share(function($app)
  {
    return new \App\Services\Router($app['events'], $app);
  });
}

And this is it! Now you can use Route::hello('/hello'); method in your routes.php file and it will work like a charm.

Pitfalls

Everything looks good but if you want to modify route handling somehow, you will notice that this code is not enough.

Problem is that Laravel registers default router practically in the beginning of it’s workflow and loads it to the Kernel before loading custom service providers. So to overwrite it we have to make a little hack.

First we need to overwrite protected function dispatchToRouter() from \Illuminate\Foundation\Http\Kernel in \App\Http\Kernel class.

This method fires when Laravel handles the route itself. But it does it after all service providers were loaded. So we can just update default Kernel router with our custom one.

<?php
/**
 * Get the route dispatcher callback.
 *
 * @return \Closure
 */
protected function dispatchToRouter()
{
  $this->router = $this->app['router'];

  return parent::dispatchToRouter();
}

But still it is not enough. If you’ll try to launch this code you will probably get an error linked to the middleware.

It happens because Kernel iterates route middlewares and saves them into the router right in it’s constructor, so when we replace it with our custom one, Laravel misses all middleware info.

So let’s update dispatchToRouter method and iterate middlewares manually.

/**
 * Get the route dispatcher callback.
 *
 * @return \Closure
 */
protected function dispatchToRouter()
{
  $this->router = $this->app['router'];

  foreach ($this->routeMiddleware as $key => $middleware)
  {
    $this->router->middleware($key, $middleware);
  }

  return parent::dispatchToRouter();
}

And now everything will work perfectly.

This method I successfully used while developing lanin/laravel-hashids composer package, that integrates Hashids lib into Laravel. You can read about it here.