Grouping Routes

So far, we’ve been defining routes in a very simple way: registering each of them in the file that automatically runs on every Concrete CMS load, application/bootstrap/app.php. There is a better way of doing this, however. Use a RouteList file.

RouteList

Instead of this in application/bootstrap/app.php:

$router->get(‘/api/users’, ‘Application\Api\Controller\UserController::getList’);
$router->get(‘/api/user/{userId}’, ‘Application\Api\Controller\UserController::read’);
$router->post(‘/api/user’, ‘Application\Api\Controller\UserController::add’);
$router->put(‘/api/user/{userId}’, ‘Application\Api\Controller\UserController::update’);
$router->delete(‘/api/user/{userId}’, ‘Application\Api\Controller\UserController::delete’);

Move this to the file Application\RouteList in application/src/RouteList.php:

<?php
namespace Application;
use Concrete\Core\Routing\RouteListInterface
use Concrete\Core\Routing\Router;
class RouteList implements RouteListInterface
{
    public function loadRoutes($router)
    {
         $router->get(‘/api/users’, ‘Application\Api\Controller\UserController::getList’);
$router->get(‘/api/user/{userId}’, ‘Application\Api\Controller\UserController::read’);
$router->post(‘/api/user’, ‘Application\Api\Controller\UserController::add’);
$router->put(‘/api/user/{userId}’, ‘Application\Api\Controller\UserController::update’);
$router->delete(‘/api/user/{userId}’, ‘Application\Api\Controller\UserController::delete’);
    }
}

Then, call this code from application/bootstrap/app.php by instantiating the object and loading the routes.

$router = $app->make(Router::class);
$list = new Application\RouteList();
$list->loadRoutes($router);

This isn’t the most complex class, but it will help to keep logic separated. And it works really nice when paired with our next concept, Route Groups.

Route Groups

Route Groups are a way to register a group of routes together with the same configuration or parameters. You can specify a route prefix, controller prefix, middlewares or other options that apply to all routes in the same group. You can define a route group by using the buildGroup() method on the Router.

$router->buildGroup()

Then chain additional methods that will apply to all routes within the group. For example, every route in the example above starts with /api and the controller namespace is always Application\Api\Controller. So let’s share those parameters:

$router->buildGroup()
->setPrefix(‘/api’)
->setNamespace(‘Application\Api\Controller’)

Next, let’s include the routes that we want in this group:

$router->buildGroup()
->setPrefix(‘/api’)
->setNamespace(‘Application\Api\Controller’)
->routes(function($groupRouter) {
    $groupRouter->get(‘/users’, ‘UserController::getList’);
    $groupRouter->get(‘/user/{userId}’, ‘UserController::read’);
    $groupRouter->post(‘/user’, ‘UserController::add’);
    $groupRouter->put(‘/user/{userId}’, ‘UserController::update’);
    $groupRouter->delete(‘/user/{userId}’, ‘UserController::delete’);
});

Much cleaner. Next, if we wanted to ensure that these routes were accessible from within an authenticated API request only, we could add some API middleware to them. Add the use statement:

use Concrete\Core\Http\Middleware\OAuthAuthenticationMiddleware;

and then:

$router->buildGroup()
->addMiddleware(OAuthAuthenticationMiddleware::class)
->setNamespace(‘Application\Api\Controller’)
// etc…

The RouteGroupBuilder allows you to specify OAuth2 scopes that are required for these routes, route requirements, and more.

Store Route Groups in Files

If you want even more separation between the routes in a group and the group definition, you can pass a string that represents a file to your routes() method in the RouteGroup.

$router->buildGroup()
->setPrefix(‘/api’)
->setNamespace(‘Application\Api\Controller’)
->routes(‘users.php’);

Then, move all the $groupRouter registration calls into application/routes/users.php.

Combine Route Lists and Route Groups

Route Lists and Route Groups are meant to be confined. The RouteList object is a simple object for registering a list of routes or route groups. Its loadRoutes() method can contain general route registrations or route group registrations. For example, here’s how the ApiRouteList class in the core looks:

class ApiRouteList implements RouteListInterface
{

    public function loadRoutes(Router $router)
    {

        $router->buildGroup()->addMiddleware(OAuthErrorMiddleware::class)
            ->routes( 'api/oauth2.php');

        $api = $router->buildGroup()
            ->setPrefix('/ccm/api/v1')
            ->addMiddleware(OAuthErrorMiddleware::class)
            ->addMiddleware(OAuthAuthenticationMiddleware::class)
            ->addMiddleware(FractalNegotiatorMiddleware::class);

        $api->buildGroup()
            ->scope('system')
            ->routes('api/system.php');

        $api->buildGroup()
            ->scope('site')
            ->routes('api/site.php');

        $api->buildGroup()
            ->scope('account')
            ->routes('api/account.php');
    }
}

Here you’ll see that not only are route groups meant to exist within a RouteList, the route groups themselves are hierarchical. First we define a set of common prefixes and middlewares, and then we build groups that load different routes with specific scopes. All these route sub-groups have the same prefix and middlewares.