Routing Utilities

Note: these docs are for the Standard Router implementation, provided with FW Api. If you are using a custom router implementation, this will likely not apply.

In order to simplify some of the route mapping as well as add a level of abstraction to the controller actions themselves, several routing utilities are included with the standard router implementation (as part of the \FW\Router\Standard\RoutingUtils trait). These utility functions are designed to be called within your Router class to make you have to know less about Slim itself in order to code an app.

The RoutingUtils is a trait, which is designed to be used by the Router.php file to give access to the functions within it to the $this object. So, to set up being able to use these functions, you would do the following:

// your-app/server/src/Router.php

namespace YourApp;

use FW\Router\Stadard\AbstractRouteMapper;
use FW\Router\Standard\RoutingUtils;

class Router extends AbstractRouteMapper {
    use RoutingUtils;
    // ...
}

Note: This is done for you by default when you generate an app with the FW Generator, so you shouldn't have to implement this, but if for whatever reason these functions aren't working, check to make sure you are properly using the RoutingUtils trait


Available Methods

The generateRouteGroup method

If you look at the Slim Framework documentation, there is a section on grouping routes. This is useful and necessary to avoid repetition when mapping a controller with multiple actions. The generateRouteGroup method encapsulates the route grouping, and provides a sane set of default maps for controllers that follow the BREAD (browse, read, edit, add, delete) pattern.

The method takes between 1-4 arguments, depending on if you want to extend it. The first argument (required) is the name of the routing group, and is used to generate a default option for the name of the Controller (see below for overriding if necessary). As is explained in the link to Slim above, this "route group" name will be the first segment of the url.

The second optional argument is an array of routes. Depending on the third argument (which is a boolean), this array will either replace or augment the default list of BREAD routes. This array needs to contain sub-arrays for each route you wish to add. The sub-array contains three elements, the first being the route name (which comes after the $name specified in the first argument), the second being the name of the action method to call on the controller, and the third being either a string or an array of HTTP verbs to use for this route (see here for an explanation of HTTP verbs, although you shouldn't need to use any other than GET, POST, PUT, or DELETE).

The third optional argument to the generateRouteGroup method is a boolean, which will determine whether or not the second argument replaces or augments the default list of routes. If set to false, it will replace the defaults, if true, it will add to them. By default it is set to false.

The fourth optional argument is a string, which will override the name of the Controller created from the route group name. You will probably rarely use this parameter if ever, but it is helpful to know it exists in case. By default, the controller name will be generated from this group name as an uppercase camelize version of the group name. For example, messages group name would by default find the Messages Controller or messageFailureLogs will by default find the MessageFailureLogs Controller. If you need it to be different than this, you must set it to the correct class name you wish to do. The only time you would use this option is if you wished for your url route name to be different than your Controller Class Name.

Example:


class Router extends \FW\Router\Standard\AbstractRouteMapper {
    use RoutingUtils;

    public function mapRoutes() {
        // This call to generateRouteGroup will
        // utilize the default list of routes,
        // meaning that it will map the routes to each of the BREAD
        // methods in the 'dept' controller
        $this->generateRouteGroup('depts');

        // This call decides to replace the default
        // list of routes with custom ones
        $this->generateRouteGroup(
            'groups',
            array(
                array('/', 'browse', 'GET'),
                array('/:id/', 'read', 'GET'),
                array('/:id/', 'edit', 'PUT'),
                array('/', 'add', 'POST'),
                array('/users/', 'getUsers', 'GET')
            )
        );

        // This call just adds the list of routes to the default BREAD routes
        $this->generateRouteGroup('threads', [
            ['/sendChannelReminderEmail/', 'sendChannelReminderEmail', 'GET'],
            ['/pinOrUnpinAll/', 'pinOrUnpinAll', 'PUT'],
            ['/reorder/', 'reorderThreads', 'PUT']
        ], true);
    }

}

It's worth mentioning here how the routes turn out in the end. As mentioned in the router intro, the "route" is what comes after the v1.0.php in your url. In the example above, the first call to the generateRouteGroup method generates 5 routes:

  • GET your-url.com/api/v1.0.php/depts/ (the browse method)
  • GET your-url.com/api/v1.0.php/depts/:id/ (the read method)
  • PUT your-url.com/api/v1.0.php/depts/:id/ (the edit method)
  • POST your-url.com/api/v1.0.php/depts/ (the add method)
  • DELETE your-url.com/api/v1.0.php/depts/ (the delete method)

The second call generates the following 5 routes:

  • GET your-url.com/api/v1.0.php/groups/ (the browse method)
  • GET your-url.com/api/v1.0.php/groups/:id/ (the read method)
  • PUT your-url.com/api/v1.0.hp/groups/:id/ (the edit method)
  • POST your-url.com/api/v1.0.php/groups/ (the add method)
  • GET your-url.com/api/v1.0.php/groups/users/ (the 'getUsers' method)

The third call generates the following 8 routes: - GET your-url.com/api/v1.0.php/threads/ (the browse method) - GET your-url.com/api/v1.0.php/threads/:id/ (the read method) - PUT your-url.com/api/v1.0.php/threads/:id/ (the edit method) - POST your-url.com/api/v1.0.php/threads/ (the add method) - DELETE your-url.com/api/v1.0.php/threads/ (the delete method) - GET your-url.com/api/v1.0.php/threads/sendChannelReminderEmail (the sendChannelReminderEmail method) - PUT your-url.com/api/v1.0.php/threads/pinOrUnpinAll (the pinOrUnpinAll method) - PUT your-url.com/api/v1.0.php/threads/reorder/ (the reorderThreads method)


The http method

In another section of the Slim Framework documentation, there is a section on dynamic route parameters. Essentially, you can put elements in your route that are converted to variables passed to the method called by the Slim framework. In the example above, several of the routes have an :id section. The : signifies it as a route parameter, and anything that goes in that section of the route would be passed as the $id value to the route function.

Because of this, the method (and its definition) that a route calls can vary widely depending on the parameters placed in the route and on what other things are passed as part of the API call.

In the slim framework documentation, it lists that a route can be mapped to a function by passing in a function directly, or an array of an object and a method name. The http method is intended to go in that place, so essentially: $this->app->get('/someRoute/', $this->http()).

The http method is automatically implemented for you in the generateRouteGroup method, so knowing what it does is integral to having a full understanding of how Routing and Controllers work in the App system even though you will hardly, if ever, call this method directly within the Router.php file of your apps, though you are able to directly call it if you find the need.

The http method has 2 required arguments.

The first two arguments are the controller classname, and the name of the action in that controller to call.

The http method wraps the actual controller action and instead passes a generic $options object (in the case of GET or DELETE methods), or both an $object array and an $options object (for POST or PUT requests).

The $options argument contains several things:

  • Any dynamic route parameters

If you have a route defined as '/depts/:id/', whatever is put in the place of ':id' in the actual call will be available on the $options object as $options->id.

For example, a call to '/depts/2/' would pass in an $options object with an $id property of 2 ($options->id == 2). Similarly, if you had a route defined as '/user/:name/' and it was called as '/user/john/', $options->name would be equal to 'john'.

  • Any query string parameters

At the end of a route call, PHP (and pretty much every web server language), allows you to append a list of key-value pairs, separated from the main route by a ?, and from each other by an &. So, /depts/2/?a=b&c=d would be converted to a PHP associative array. See here for more info on query strings.

This array of query string parameters is put on the $options object as $options->query, so using the example above, $options->query would essentially equal: array('a' => 'b', 'c' => 'd').

  • Any specific option values that are specified on the controller.

Sometimes, you may want a particular query string variable to always have a value, or to handle data in a specific way when a particular query string is present. One specific example might be a limit value in your query string, which could limit the data returned by some constraint:

'/depts/?limit=10' => you could call this if you only wanted the first 10 results to be returned.

In your controller, you would need to define an $allowedOptions array (as a public static variable), which will tell the http method which options are allowed in such a manner. Also, you can define a default value for said option by specifying another public static variable with a name that matches the option in the $allowedOptions array (in the previous example, to specify a default value for the limit, option, one would supply a public static $limit variable and set its value to what the desired default is.)

Each allowed option is defined as a variable on the $options object, so the limit value from before would be available as $options->limit.

Also, if the query string contains a key-value pair in which the value contains multiple strings separated by commas (example: ?includes=groups,users,roles), the http method will automatically convert them into an array (so $options->includes = array('groups', 'users', 'roles')).

NOTE: there is a default value for the $allowedOptions array, which is 'includes', meaning that 'includes' is always an allowed option. Define a public static $includes variable to set defaults for it

ALSO OF NOTE: Any query string key-value pairs that match an allowedOption are removed from the query string array, meaning that they won't be available as part of the $options->query array


If the request being made is a POST or PUT request, the $options object will be the second argument passed to the function. The first will be an $object array, which contains any and all data passed to the server through the http form data or as a JSON string.

To exemplify this http method, look at the following example:

// Router.php

class Router extends \FW\Router\Standard\AbstractRouteMapper {
    use RoutingUtils;

    public function mapRoutes() {
        //this is technically how you would implement the http method directly,
        //but you will hardly ever need to do that as generateRouteGroup will
        //automatically implement the http function for you.
        $this->app->get('/depts/', $this->http('Departments', 'browse'));

        $this->app->get('/depts/:id/', $this->http('Departments', 'read'));

        $this->app->post('/depts/', $this->http('Departments', 'add'));
    }
}

// Controllers/Departments.php
class DepartmentsController extends \FW\Router\Standard\AbstractController {

    // here we're setting the allowed options for this controller
    public static $allowedOptions = array('limit');

    // specifying a default value for limit
    public static $limit = 'all';

    // even though it isn't in allowedOptions in this controller,
    // $includes is always an allowed option so we define its default
    // here
    public static $includes = array('groups', 'users', 'roles');


    // here are the action methods
    public function browse($options) {
        // if the route call were made like this: '/depts/'
        echo $options->limit;
        // then this would output the default value 'all'
    }

    public function read($options) {
        // if the request were made like so: '/depts/2/'
        echo $options->id;
        // this would output '2'
    }

    public function add($object, $options) {
        // if the POST request contained the following keys and values (as part of the request body)
        // name: 'Some Department'
        // id: 'somedept'
        var_dump($object);
        // this would output:
        // array(
        //     'name' => 'Some Department',
        //     'id' => 'somedept'
        // )
    }
}

Other things to consider about the http method

  • As said above, any routes generated using the generateRouteGroup method automatically use the http method to wrap each route.

  • If a controller action returns an instance of the standard view implementation, it will be output, meaning you don't have to echo it manually inside the action.

  • If a request is a POST request and you don't throw an exception within the controller action, the HTTP status code will automatically be set to 201 (the 'Created' status code). This is done for conventional purposes.