Taking Laravel Routes one step further.

This blog article was inspired by Ewan Valentine’s post about Cleaning up Laravel Routes.

In this article, we are going to take Laravel Routes one step further by using the trick Ewan’s co-worker found and use it to make Laravel Routing even more easier.

Please note, that I’m currently working on a project where this routing approach makes sense so far. It could however be, that it doesn’t fit your needs or that you need to expand this class even more. So let’s start. I will skip the part with the RouteServiceProvider.php because it’s the same as in the linked blog article and you should give Ewan credit for it, not me.

So let’s assume now, that we already got the RouteServiceProvider.php files set up correctly. What we slightly do different from the article is how we handle the Routing in the classes itself. Currently, we need to write all Routes into a separate class which sometimes has the same logic all over again. What I’m trying to do is to minify this so we don’t repeat ourself too much when writing the routes.

Routes.php
This class will be used as a base class. If you have a GameController and want to create routes for it, you would create the new class GameRoutes which extends the class Routes.

If you scroll down, you can see the whole class code. I’ll try to explain the way it works in the correct order. So, what does it do? We have the map method, which is used to map the requests to the Controller. Nothing special here, the same as before. But we are doing something different inside the method.

The method iterates over an array of available actions. In the Routes class those actions are the default actions you will use most of the time when building a basic CRUD-API or a website with basic functionality. The actions are used as the array index and the values of the actions are some settings like:

  • Which method to use (This is the method of the Registrar class
  • The path for the action and method
  • Additional settings (such as namespaces, middleware, etc)

The map method then creates an array from the options in the array and merges it with the Controller data. It basically builds the routing conditions from the array and the variables set in the inherited classes.

So, here’s the full class

namespace App\Http\Routes;

use Illuminate\Contracts\Routing\Registrar;

class Routes
{
    /**
     * The base path for the routing (e.g. games)
     *
     * @var string
     */
    protected $basePath;

    /**
     * The name of the Controller (e.g. GameController)
     *
     * @var string
     */
    protected $controller;

    /**
     * Defines the actions that can be used to be handled by the resource controller
     *
     * @var array
     */
    protected $actions = [
        'index' => [
            'method' => 'get',
            'path' => '',
            'settings' => [],
            
        ],
        'create' => [
            'method' => 'get',
            'path' => 'create',
            'settings' => [],
        ],
        'store' => [
            'method' => 'post',
            'path' => '',
            'settings' => [],
        ],
        'show' => [
            'method' => 'get',
            'path' => '/{id}',
            'settings' => [],
        ],
        'edit' => [
            'method' => 'get',
            'path' => '/{id}/edit',
            'settings' => [],
        ],
        'destroy' => [
            'method' => 'delete',
            'path' => '/{id}',
            'settings' => [],
        ],
    ];

    /**
     * The actions that will not be used in the routing process (so if you want to disable some methods for a Route)
     *
     * @var array
     */
    protected $disabledActions = [];

    /**
     * Maps the actions to the routes
     *
     * @param Registrar $router
     */
    public function map(Registrar $router)
    {
        foreach ($this->actions as $action => $options) {
            if (in_array($action, $this->disabledActions)) {
                continue;
            }

            $settings = array_merge($options['settings'], [
                'uses' => $this->controller . '@' . $action
            ]);

            $router->{$options['method']}($this->basePath . $options['path'], $settings);
        }
    }

    /**
     * Sets the path for the route for the action
     *
     * @param string $action
     * @param string $path
     */
    public function setActionPath(string $action, string $path)
    {
        $this->setActionHelper($action, 'path', $path);
    }

    /**
     * Sets settings for specific actions
     *
     * @param string $action
     * @param array $settings
     */
    public function setActionSettings(string $action, array $settings)
    {
        $this->setActionHelper($action, 'settings', $settings);
    }

    /**
     * Sets a specified helper to a value for an action
     *
     * @param string $action
     * @param string $helper
     * @param mixed $value
     */
    protected function setActionHelper(string $action, string $helper, $value)
    {
        if (isset($this->actions[$action]) && isset($this->actions[$action][$helper])) {
            $this->actions[$action][$helper] = $value;
        }
    }
}

As you can see, there are two variables $basePath and $controller which I haven’t explained as well as some more methods. I will explain them using an example. Let’s go back to creating our GameController’s routes.

GameRoutes.php

We would set the $basePath with the value we want it to be displayed in the URL. So when you got URLs like /games, /games/1, /games/1/edit, etc you would set the $basePath to games. The $controller variable will hold the name of the controller (without namespaces), so in this example, it would be GameController.

But now we would have a problem if we would need to set additional settings for specific methods, right? So what we do is overwriting the map method and setting the settings for the specific action with the setActionSettings method. We have the same method for the path (if it changes). Maybe you don’t want to use POST /games for storing but instead POST /games/store. This could be achieved for that method.

Here is the full class

namespace App\Http\Routes;

use Illuminate\Contracts\Routing\Registrar;

class GameRoutes extends Routes
{
    /**
     * The base path for the routing (e.g. games)
     *
     * @var string
     */
    protected $basePath = 'games';

    /**
     * The name of the Controller (e.g. GameController)
     *
     * @var string
     */
    protected $controller = 'GameController';

    /**
     * Maps the actions to the routes. Sets the options for the store method so we need to use authentication
     *
     * @param Registrar $router
     */
    public function map(Registrar $router)
    {
        $this->setActionSettings('store', [
            'middleware' => 'auth'
        ]);

        parent::map($router);
    }
}

The last thing I would like to note is what we do, when we want to use groups. E.g. sometimes you want to use namespace groups or subdomain groups. This is my code of a Routing class which uses a subdomain grouping

public function map(Registrar $router)
    {
        $router->group(['namespace' => 'Api', 'domain' => 'api.domain.dev' ], function() use($router) {
            parent::map($router);
        });
    }

This works without any more configuration. If you however have even more special cases you can overwrite the map method completely and implement your own logic as you wish. For example, if you don’t want some methods to be routed, you can overwrite the $disabledActions array with the methods you don’t need.

If you found a way to make this even better don’t hesitate to contact me! Feedback is appreciated. We are all here to help each other out.

It's only fair to share...Share on FacebookTweet about this on TwitterShare on RedditEmail this to someoneShare on Tumblr

Leave a Comment

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.