Lightweight URL Router

v2.0.10 2018-02-12 00:17 UTC

README

Lightweight url router with dependency injection support.

Instalation

Add the dependency to composer.json, then composer install

{
    "require": {
        "objectiveweb/router": "~2.0"
    }
}

Basic Usage

In Objectiveweb applications, an endpoint refers to a php file which responds to url routes.

Under the hood, the route($regex, $callable) function tests $regex against the current request method and uri (e.g. "GET endpoint.php/something/([0-9]+)/?"), passing the captured parameters to $callable when matched.

Example endpoint

<?php
// include the composer autoloader
require_once('../vendor/autoload.php')

use Objectiveweb\Router;

$app = new Router();

// Routes are matched in order

$app->GET('/?', function() {
    echo "Hello index';
});

$app->GET('/([a-z]+)/?', function($key) {

    switch($key) {
        case 'data':
            // If the callback returns something, it's sent with status 200 (OK)
            // Arrays are automatically encoded to json
            return [ 1, 2, 3 ];
            break;
        default:
            throw new \Exception([ 'key' => $key, 'empty' => true ], 404); // respond with custom code
            break;
    }
});

$app->POST('/panic', function() {
    // Exceptions are captured, message is send with the proper code
    throw new \Exception('Panic!', 500);
});

// catch all
$app->router("([A_Z]+) (.*), function ($method, $path) {
    return "Request matched $method and $path";
});

Controllers

PHP classes may be bound to an url using the controller($path, $class) on public-facing endpoint (index.php)

$app->controller('/', 'ExampleController');

In this case, the request is mapped to the corresponding class method as follows

<?php

class ExampleController {

    // GET /?k=v
    function index($querystring) {
    
    }
    
    // GET /(.*)?k=v
    function get($path1, $path2, ..., $querystring) {
    
    }
    
    // Runs before all actions
    function before() {
        
    }
    
    // Runs before post();
    function beforePost($body) {
    
    }
    
    // POST /
    function post($body) {
    
    }
    
    // Other request methods are also valid, i.e. head(), options(), beforeHead(), etc
    
}

When a function named like the first parameter ($path[0]) exists on the controller, it gets called with the remaining parameters

// (GET|POST|PUT|...) /example/(.*)
function example($path[1], $path[2], ...) {
    // check $_SERVER['REQUEST_METHOD'] and process data
}

This function name may also be prefixed with the request method. In this case the query parameters are passed as the last argument

// GET /example/(.*)
function getExample($path[1], $path[2], ..., $_GET) {

}

// POST /example/(.*)
function postExample($path[1], $path[2], ..., $decoded_post_body) {

}

Other request methods are also valid (i.e. HEAD, OPTIONS, etc), check the example subdir for other uses.

Automatic routing

You can bootstrap the application on a particular namespace using

$app->run('Namespace');

When run, the Router automatically maps the incoming requests to the given namespace. For example, a request to /products would instantiate the Namespace\ProductsController class.

If the Controller doesn't exist, the request is passed to the Namespace\HomeController. Check example/app-run.php for a working demo.

Dependency Injection

Since version 2.0, the Router extends Dice, which provides a dependency injection container to the application.

<?php
// include the composer autoloader
require_once('../vendor/autoload.php')

use Objectiveweb\Router;

$app = new Router();

// Configure the DI container rules for the PDO class
$app->addRule('PDO', [
    'shared' => true,
    'constructParams' => ['mysql:host=127.0.0.1;dbname=mydb', 'username', 'password'] 
]);

// From now on, you can get a configured PDO instance using
$pdo = $app->create('PDO');

When bound to paths, Controller (And dependencies of those dependencies) get automatically resolved. For example, if you define the controller

<?php

namespace MyApplication;

class MyController {

    private $pdo;
    
    function __construct(PDO $pdo) {
        $this->pdo = pdo;
    }
    
    function index() {
        // query the database using $this->pdo
    }
}

When MyApplication\MyController gets instantiated by the Router, a configured instance of PDO will be injected and reused as necessary.

You can inject dependencies adding type-hinted parameters to your controller's constructor:

function __construct(\Util\Gmaps $gmaps, \DB\ProductsRepository $products) {
  $this->gmaps = $gmaps;
  $this->products = $products;
}

// Use $this->gmaps and $this->products on other functions

In a another example, let's instantiate Twig

// index.php

$app = new \Objectiveweb\Router();

$app->addRule('Twig_Loader_Filesystem', array(
    'shared' => true,
    'constructParams' => [ TEMPLATE_ROOT ]
));

$app->addRule('Twig_Environment', array(
    'shared' => true,
    'constructParams' => [
        [ 'instance' => 'Twig_Loader_Filesystem' ],
        [ 'cache' => APP_ROOT.'/cache' ],
        [ 'auto_reload' => true ]
    ],
    'call' => [
        [ 'addGlobal', [ 'server', $_SERVER['SERVER_NAME'] ] ],
        [ 'addGlobal', [ 'app_name', APP_NAME ] ],
        [ 'addGlobal', [ 'session', $_SESSION ] ],
        [ 'addFunction', [ new Twig_SimpleFunction('url', function ($path) {
            return \Objectiveweb\Router::url($path);
        })]]
    ]
));

$app->controller('/', 'MyController')

Then, inject it on your controller's constructor

class MyController {

    private $twig;
    private $pdo;
    
    function __construct(Twig_Environment $twig, PDO $pdo) {
        $this->twig = $twig;
        $this->pdo = $pdo;
    }
    
    function index() {
        return $this->twig->render(...);
    }
}

You can also fetch the twig reference using

$twig = $app->create('Twig_Environment');

Rules

Dice Rules can be configured with these properties:

  • shared (boolean) - Whether a single instance is used throughout the container. View Example
  • inherit (boolean) - Whether the rule will also apply to subclasses (defaults to true). View Example
  • constructParams (array) - Additional parameters passed to the constructor. View Example
  • substitutions (array) - key->value substitutions for dependencies. View Example
  • call (multidimensional array) - A list of methods and their arguments which will be called after the object has been constructed. View Example
  • instanceOf (string) - The name of the class to initiate. Used when the class name is not passed to $app->addRule(). View Example
  • shareInstances (array) - A list of class names that will be shared throughout a single object tree. View Example