8
\$\begingroup\$

Below is what I have come up with for a router/dispatcher system for my personal framework I am working on. Can you please review and tell me any improvements that could be made?

The first part is an array of URI -> to class/method/id_number/page_number using regex. I have only included a partial list of routes, there will be at least 50 possible routes that will have to run the regex on. I am thinking that is pretty bad for performance, but it seems the best way I know of to do it, since I need to match page numbers and id numbers when they exists.

I am new to MVC so this is my first attempt and I am sure you guys can give me improvement on this, thanks for any tips or help!

.htaccess file:

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$   index.php?uri=$1    [NC,L,QSA]

Map array():

/**
 * Map URI to class/method and ID and Page numbers
 * Must be an array
 */
$uri_route_map = array( 
    //forums
    'forums/' => array(
        'controller' => 'forums',
        'method' => 'index',
        'id_number' => '',
        'page_number' => ''),

    'forums/viewforum/(?<id_number>\d+)' =>  array(
        'controller' => 'forums',
        'method' => 'viewforum',
        'id_number' => isset($id_number),
        'page_number' => ''),  

    'forums/viewthread/(?<id_number>\d+)' =>  array(
        'controller' => 'forums',
        'method' => 'viewthread',
        'id_number' => isset($id_number),
        'page_number' => ''),

    'forums/viewthread/(?<id_number>\d+)/page-(?<page_number>\d+)' =>  array(
        'controller' => 'forums',
        'method' => 'viewthread',
        'id_number' => isset($id_number),
        'page_number' => isset($page_number)),

    // user routes
    // account routes
    // blog routes 
    // mail routes
    // various other routes
);

Router class that reads and matches the Map array above

/**
 * Run URI against our Map array to get class/method/id-page numbers
 */
 class Router
{
    private $_controller = '';
    private $_method = '';
    public $page_number = '';
    public $id_number = '';

    public function __construct($uri, array $uri_route_map)
    {
        foreach ($uri_route_map as $rUri => $rRoute)
        {
            if (preg_match("#^{$rUri}$#Ui", $uri, $uri_digits))
            {
                //if page number and ID number in uri then set it locally
                $this->page_number = (isset($uri_digits['page_number']) ? $uri_digits['page_number'] : null);
                $this->id_number = (isset($uri_digits['id_number']) ? $uri_digits['id_number'] : null);
                $this->_controller = $rRoute['controller'];
                $this->_method = $rRoute['method'];

                // just for debug and testing while working on it / will be removed from final code
                echo '<hr> $page_number = ' . $this->page_number . '<br><br>';
                echo '<hr> $id_number = ' . $this->id_number . '<br><br>';
                echo '<hr> $controller = ' . $this->_controller . '<br><br>';
                echo '<hr> $method = ' . $this->_method . '<br><br>';
                break;
            }else{
                $this->page_number = '';
                $this->id_number = '';
                $this->_controller = '404';
                $this->_method = '404';
            }
        }
    }

    public function getController()
    {
        return $this->_controller;
    }

    public function getMethod()
    {
        return $this->_method;
    }

    public function getPageNumber()
    {
        return $this->page_number;
    }

    public function getIDNumber()
    {
        return $this->id_number;
    }

    /**
     * Call our class and method from values in the URI
     */
    public function dispatch()
    {
        if (file_exists('controller' . $this->_controller . '.php'))
        {
            include ('controller' . $this->_controller . '.php');
            $controllerName = 'Controller' . $this->_controller;
            $controller = new $controllerName($this->getIDNumber(),$this->getPageNumber());
            $method = $this->_method;
            if (method_exists($this->_controller, $this->_method))
            {
                return $controller->$method();
            } else {
                // method does not exist
            }
        } else {
            // Controller does not exist
        }
    }

}

Run it

/**
 * Testing the class
 */
$uri = isset($_GET['uri']) ? $_GET['uri'] : null;
$router = new Router($uri, $uri_route_map);
$router->dispatch();

?>
\$\endgroup\$
7
\$\begingroup\$

If you can limit your URI structure to using a delimiter - / comes to mind - you could avoid the regex.

Here's a rough example (not with any configuration, but shows the concept):

$uri = 'forums/viewforum/1';
$parts = explode('/', $uri);

$controller = $parts[0];
$method = $parts[1];
$id = parts[2];

I'd take a look at the router implementations of some popular frameworks (my recommendation would be ZF's router, but that's just me) - there's nothing wrong from learning how someone else tackled the same problem.

\$\endgroup\$
2
\$\begingroup\$

Also check out 'Silex' and 'Slim' which are microframeworks which include routing to see how they tackle this problem. Personally I think that just having id_number and page_number is restrictive. Eventually you'll want more complexity (and potentially params) passed in each route.

Silex for example maps each route to a closure, which is a pattern that I think works pretty well.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.