Cleaning up Laravel Routing



Here at 65twenty we've been working on a number of pretty large projects, one of which is a Laravel based API. We chose Laravel for its ease of use, its active community, and rapid rate of development. I don't have a lot to fault Laravel on. But I started to get nauseous just looking at our routes folder.

My first suggestion (coming from a Symfony background), was to suggest using this annotations library. Now I love annotations. I think they keep configuration decoupled and with the code it affects. It feels neater having all of an endpoints events, routing etc right there above the code. Though people are rightly suspicious of them. They're not native to PHP, and plans for them to be were dropped from PHP 7's feature list. So annotations were shot down by almost everyone in the team. The best shootdown being:

So we still had the issue of a messy routing file. That was until a another engineer in my team pointed out this nifty trick he spotted in a project his friend was working on.

Let's take a look at that in greater detail:

<?php

namespace SixtyFiveTwenty\Providers;

use Illuminate\Routing\Router;  
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;

/** 
 * RouteServiceProvider
 *
 * @author    Ewan Valentine <ewan@theladbible.com>
 * @copyright 65twenty 2015
 */
class RouteServiceProvider extends ServiceProvider  
{
    /**
     * This namespace is applied to the controller routes in your routes file.
     *
     * In addition, it is set as the URL generator's root namespace.
     *
     * @var string
     */
    protected $namespace = 'SixtyFiveTwenty\Http\Controllers';

    /**
     * Define your route model bindings, pattern filters, etc.
     *
     * @param  \Illuminate\Routing\Router  $router
     * @return void
     */
    public function boot(Router $router)
    {
        parent::boot($router);
    }

    /**
     * Define the routes for the application.
     *
     * @param  \Illuminate\Routing\Router  $router
     * @return void
     */
    public function map(Router $router)
    {
        $router->group(['namespace' => $this->namespace], function ($router) {

            // Look under Http\\Routes for all php files, map to container.
            foreach(glob(app_path('Http/Routes').'/*.php') as $file) {
                $this->app->make('SixtyFiveTwenty\\Http\\Routes\\'.basename($file, '.php'))->map($router);
            }
        });
    }
}

So what's going on here? We're overriding the default behaviour of Laravels routing to be a bit more clever. We're now targeting a Routes directory and scanning for any classes within this new Routes directory. There's still a little more set-up to do in order to load our routes into our application.

So here's one of our new route files:

<?php

namespace SixtyFiveTwenty\Http\Routes;

use Illuminate\Contracts\Routing\Registrar;

/**
 * Routes
 *
 * @author    Ewan Valentine <ewan@theladbible.com>
 * @copyright 65twenty 2015
 */
class Routes  
{
    public function map(Registrar $router)
    {
        $router->get('/test', 'TestController@index');
    }
}

You'll notice they're now in a class and have a map function containing our routes, and the routing object is dependency injected into that class. So instead of having your routes in one file, which get loaded into a single route mapping. We're creating multiple mapping instances and then combining them into the applications route collection.

This has several advantages, the main one being you can properly separate routing into their own classes. Which makes things a little easier on the eyes. Also as you're injecting the routing service and not using facades, it should be a little easier to test.