The Router class is used to resolve a RESTful style endpoint to an action. The action is either a Closure function, or a Controller/Method combination. When no routers matched the current URI of the request the default action is a 404 page no found response.

The router class is a factory service in the container. It works like this, every time you call a method chain to resolve a endpoint a new Disco\classes\Router instance is instantiated and used as the starting point for the method chain of the call. (Method Chaining is simply the calling of multiple methods in a single line of code execution. It can be achieved by returning $this from a function of a object.) Once a router has matched the request the container will no longer return a factory service, but a standard service returning a MockBox instance.

Basic GET/POST/PUT/DELETE Examples #

// Routers call a Closure function or a class@method combo

// GET Requests
Router::get('/products', function(){
    // Perform logic
});

// POST Requests
Router::post('/products', 'Product@postProducts');

// PUT Requests
Router::put('/products', 'Product@putProducts');

// DELETE Requests
Router::delete('/products', function(){
    // Perform Logic
});

// ANY type of of Request
Router::any('/products', 'Product@anyProduct');

// Multiple requests to same uri
Router::multi('/products', [
    'get' => 'Product@getProduct',
    'post' => 'Product@postProduct'
])

Conditions #

// You can extract variables from URIs and force adherence to data types (via regex or predefined conditions) in order for routers to match
Router::get('/products/{category}/{id}', 'App\controller\Product@getProduct')
    ->where([
        'category' => '^(food|hardware|software)+',
        'id' => 'integer'
    ]);

Router::put('/message/{user_id}-{name}', function($user_id,$name){
    // Do something with the $user_id and PUT data
})->where([
    'user_id' => 'integer_positive',
    'name' => 'alpha_numeric'
]);


// You can also allow optional get variables eg: /your-uri?foo=bar&bar=foo
// by using the allowURLParameters() method and passing it a single string key, or an array of string keys
Router::get('/products/', 'App\controller\Product@getIndex')->allowURLParameters(Array('foo','bar'));

Instead of making you repeat common regex patterns constantly we have provided a few default ones for your use.

  • alpha => '^[a-zA-Z\s\-]+$'
  • alpha_nospace => '^[a-zA-Z\-]+$'
  • alpha_numeric => '^[a-zA-Z\s\-0-9]+$'
  • alpha_numeric_nospace => '^[a-zA-Z\-0-9]+$'
  • integer => '^-?\d+$'
  • integer_positive => '^\d+$'
  • numeric => '^-?\d+(\.\d+)?$'
  • numeric_positive => '^\d+(\.\d+)?$'
  • all => '[.]*'
  • datetime => '^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$'
  • date => '^\d{4}-\d{2}-\d{2}$'
  • time => '^\d{2}:\d{2}:\d{2}$'
  • boolean => '^(true|false)$'
  • one_or_zero => '^(1|0)$'

You can add your own matching conventions to the default list:

App::addCondition('numbersAndDashes', '^[0-9\-]+$');
App::addCondition('lclettersAndDashes', '^[a-z\-]+$');

 

HTTPS Requests #

// You can make routers only match HTTPS requests by using the secure method
Router::secure()->get('/', 'App\controller\Root@getIndex');

Router::secure()->post('/signup','App\controller\User@postSignup');

Un-Match Routes #

// Return false from your Controller or Closure function to un-match the route
Router::get('/bad-url', function(){
    return false;
});

// Or at any time you can call the routeMatch method

if(Router::routeMatch()){
    // We have a matching route for this request!
}//if

// Unmatch it
Router::routeMatch(false);

// Force a match
Router::routeMatch(true);

Authentication/Protecting Routes #

Use the auth() method of the router to only match routes when the existence of specified sessions exist. You can also have the router perform automatic redirections to a specific page when the sessions do not exist.

// Basic single route authentication with an `admin` session key existing.
Router::auth('admin')->get('/admin/', 'Admin@getIndex');

// Basic single route authentication with an `admin` or `super` session key existing.
Router::auth(['admin','super'])->get('/admin/', 'Admin@getIndex');

// Basic single route authentication with an `admin` session key existing
// redirecting to the `/login` route when session doesn't exist
Router::auth('admin','/login')->get('/admin/', 'Admin@getIndex');

// Combine the auth method with the filter method to protect whole segments of your site
Router::auth('admin','/login')->filter('/admin/{*}')->to('AdminRouter');

Filter Routes #

You can filter directories of your application really easy using the filter() method of the Router. A filter can be used to force the processing of a Router File (see router files below), or execute a closure function which stores other Routers. The reasoning behind filters is so that you can funnel all routes to a single point of entry and wrap them in specific logic. It will also speed up your application because fewer routers have to be processed to determine which router actually needs to be executed based on the current URI request to the application.

To specify what should be filtered we use the special variable {*}.

To specify what action should be taken when a filter is matched, you use the to method, or pass the action as the section parameter to the filter function. Pass it either a Router File name like ShoppingRouter which is saved at app/router/ShoppingRouter.router.php, or pass it a Closure function.

// Grouping routes for speed
Router::filter('/user/{*}')->to(function($filter_lead, $filter_tail){
    // $filter_lead = `/user/`;
    // $filter_tail = the trailing part of the URI

    Router::get($filter_lead . 'dashboard', 'User@getDashboard');
    Router::get('/user/inbox', 'User@getInbox');
    Router::post('/user/inbox', 'User@postInbox');

});


// The secured shopping area of the application 
Router::secure()->filter('/shopping/{*}')->to('ShoppingRouter');

// Now that we matched the secure shopping area lets redirect the non HTTPS requests to the proper HTTPS route
Router::filter('/shopping/{*}', function(){
    $url = 'https://' . App::config('URL');
    header("Location: {$url}/shopping;");
    exit;
});


// Force HTTPS and an `admin` session to access `/admin/{*}` routes,
// redirecting them to the `/login` route if they have no `admin` session going
Router::secure()->filter('/admin/{*}', 'AdminRouter')->auth('admin','/login');

Router Files #

To keep your code clean and logically separated you can (and should) store your routers in different files, named for the routes they contain. Router files are stored in `app/router/` and use the naming convention `YourRouter.router.php`, however if a router name is supplied using an alias, it can be stored where ever the alias points to and doesn't have to have use the `.router.php` postfix.

Your router files can either return an array of routes which are defined using exactly the same syntax as children routes (but without the inheritance of URIs and where conditions), or by defining any number of routers as standard php code.

Router files can be loaded via the useRouter() method, or by passing the router file name to a filter route.

// Standard use case, expecting a file stored at `app/router/User.router.php`
Router::useRouter('User');
// same can be supplied to a filter
Router::filter('/user/{*}')->to('User');
// Or
Router::filter('/user/{*}','User');

// Or can use an alias
App::registerAlias('user.router','app/router/user/');
Router::filter('/user/{*}')->to('@user.router:User.php');

Children Routes #

URIs often mirror directory or folder structures, in which files or other directories are children/descendants of their parents. Child routes are just this, routes that exist within the URI of the childs parent. They inherit multiple properties from their parents as well.

Children routers take the form of an array where the key is the URI relative to the parents URI and its value is another array describing the action, the keys and value of this nested array can be as described:

  • type - (required) The type of action to perform, can be any of these values:
    • get
    • post
    • put
    • delete
    • multi
    • filter
  • action(required) The action to perform, either a Closure function, a string specifying a Controller and method combo, a string specifying a router file to load and use, or an array of routes (only applicable to filter routes).
  • where - An array of where conditions the URI must meet to be a match.
  • allowURLParameters - A string defining a single get parameter, or an array of get parameters the route supports.
  • auth - A string defining a session key that must exist, or an array with keys `session` with a string value for a session key that must exist and `redirect` for where to redirect when there is no session.
  • secure - A boolean defining whether the request must have been made via HTTPS.
  • children - An array of children of the route, or a string specifying a router file which contains children of the route.

Its important to understand that URIs defined by child routes will have their URIs concatenated with their parents, so they are always defined relative to their parents URI. This is also the case with any where conditions specified by the parent route, the child route will have its where conditions merged with the parents and any children controllers should expect to receive variables parsed from the URI in the order they are defined (from left to right) in the URI. This also means it's important not to name where conditions in the child route the same as any where conditions used by any of its parents 

// A route that has children
Router::get('/product/{id}', 'Product@getProduct')
    ->where('id','integer_positive')
    ->children([
        '/reviews' => [
            'type' => 'get',
            'action' => 'Product@getProductReviews'
        ],
        '/leave-review' => [
            'type' => 'multi',
            'auth' => [ 'session' => 'user', 'redirect' => '/login'],
            'action' => [
                'get' => 'User@getLeaveProductReview',
                'post' => 'User@postLeaveProductReview'
            ]
        ],
        '/compare-with/{other_product_id}' => [
            'type' => 'get',
            'where ' => ['other_product_id' => 'integer_positive'],
            'action' => function($id, $other_product_id){
                // notice the parameters passed to the closure
            }
        ]
    ]);

// Filtering to children
Router::filter('/user/{*}')->children([
    'profile' => [
        'type' => 'get',
        'action' => 'User@getProfile',
        'children' => [
            '/history' => [
                'type' => 'filter',
                'action' => 'UserProfileRouter'
            ]
        ]
    ]
]);

// Filter to children stored in another router (see router files below)
Router::filter('/user/{*}')->children('UserChildrenRouter');

Paginate #

You can use the paginate method of the router to specify that a route is being used to perform pagination. The route will automatically be set up to handle pagination with the default format /page/{page} or the custom format you provided in your application configuration via the key paginate (your custom format must contain the string {page} to work). Routes defined using the pagination method are intended to work with the Pagination class and the Twig Template tag {% page %}, but can work just as well without them.

Lets take a look at an example:

// The routers paginate method will automatically use the 
// default pagination format or your configured pagination format 
// to build and resolve the appropriate URIs
Router::paginate('/events/{type}', function($type, $page){
    // perform your pagination action 
})->where('type', '(music|catering|wedding)');

This will match all routes to both /events/{type} and /events/{type}/page/{page}. The page is set to be a positive integer and will always be passed as the last argument to your Closure or Controller Method. If a negative number is passed in the page the request will 404, and if page 0 is passed it will be redirected to page 1. If the request is matching just the base ie /page/{type} the page will be set to 1. You can use any of the other Router methods in conjunction with the pagination method.

Be sure to check out the Paginate class docs for a full example of integrating the Router pagination method with the rest of the framework to make paginated results an absolute dream.