Reputation: 1509
I am trying to write my own Router as I embark on my MVC journey. I have the basics of it down, but I am running into a bit of a snag when it comes to additional URL parameters. I have no idea how to approach this based on how I have written my code thus far.
Here is my code:
public/index.php
<?php
include '../config.php';
include '../routes.php';
include '../app/controllers/RouteController.php';
$rc = new RouteController($config, $routes);
?>
config.php
<?php
$config['app_url'] = 'http://localhost/mymvc'; // no ending slashes
?>
routes.php
<?php
$routes = array();
$routes[] = array('url' => '', 'controller' => 'HomeController');
$routes[] = array('url' => 'leads', 'controller' => 'LeadController');
$routes[] = array('url' => 'leads/page/[0-9]', 'controller' => 'LeadController', 'method' => 'update', 'params' => 'page=$1');
$routes[] = array('url' => 'leads/create', 'controller' => 'LeadController', 'method' => 'create');
$routes[] = array('url' => 'leads/update/[0-9]', 'controller' => 'LeadController', 'method' => 'update', 'params' => 'id=$1');
?>
app\controllers\RouteController.php
<?php
class RouteController {
private $config;
private $routes;
private $url;
private $controller = null;
private $method = null;
private $params = array();
public function __construct ($config, $routes) {
$this->config = $config;
$this->routes = $routes;
$this->setUrl();
$route_exists = false;
// check if route has been declared for security
foreach ($this->routes as $route) {
if ($route['url'] == $this->url) {
$this->controller = $route['controller'];
$this->method = isset($route['method']) ? $route['method'] : null;
$this->params = isset($route['params']) ? $route['params'] : array();
$route_exists = true;
}
}
// send them to app index if route does not exist
if ($route_exists) {
$this->route();
}
else {
header('Location: '.$this->config['app_url']);
die('route does not exist');
}
}
private function setUrl () {
$url = trim($_GET['url'], '/');
$url = filter_var($url, FILTER_SANITIZE_URL);
$this->url = $url;
}
private function route () {
// include '../controllers/'.$this->controller;
// do stuff with the controller, method, params
}
}
?>
Now, if you look at the routes.php file, you can see from my code that it has no problem with the following routes:
<?php
$routes = array();
$routes[] = array('url' => '', 'controller' => 'HomeController');
$routes[] = array('url' => 'leads', 'controller' => 'LeadController');
$routes[] = array('url' => 'leads/create', 'controller' => 'LeadController', 'method' => 'create');
?>
What I need to figure out is how I can approach additional parameters in my routes. These are the problematic routes I am trying to find a solution for:
$routes[] = array('url' => 'leads/page/[0-9]', 'controller' => 'LeadController', 'method' => 'update', 'params' => 'page=$1');
$routes[] = array('url' => 'leads/update/[0-9]', 'controller' => 'LeadController', 'method' => 'update', 'params' => 'id=$1');
Any help would be greatly appreciated. Thank you :)
Upvotes: 3
Views: 4445
Reputation: 3737
Will try to demonstrate on my example. Bootstrap
Class:
$uri = Router::make_uri();
if ($params = Router::match_uri($uri))
{
$controller = ucwords($params['controller']).'_Controller';
$method = $params['method'];
unset($params['controller'], $params['method']);
if (class_exists($controller))
{
if (method_exists($controller, $method))
{
call_user_func_array(array(new $controller, $method), $params);
}
else
{
Error::throw_error('Bootstrap : No method found '.$method);
}
}
else
{
Error::throw_error('Bootstrap : No controller found '.$controller);
}
}
else
{
Error::throw_error('Bootstrap : No route found '.$uri);
}
And Router
Class:
public static function make_uri()
{
if(!empty($_SERVER['PATH_INFO']))
{
self::$uri = $_SERVER['PATH_INFO'];
}
elseif (!empty($_SERVER['REQUEST_URI']))
{
self::$uri = $_SERVER['REQUEST_URI'];
//removing index
if (strpos(self::$uri, 'index.php') !== FALSE)
{
self::$uri = str_replace(self::$uri, 'index.php', '');
}
}
return parse_url(trim(self::$uri, '/'), PHP_URL_PATH);
}
public static function match_uri($uri)
{
require(APP_DIR.DIR_SEP.'system'.DIR_SEP.'config'.DIR_SEP.'Routes.php');
if (empty($routes))
{
Error::throw_error('Routes must not be empty');
}
self::$routes = $routes;
$params = array();
foreach ($routes as $route)
{
//we keep our route uri in the [0] position
$route_uri = array_shift($route);
$regex_uri = self::make_regex_uri($route_uri);
if (!preg_match($regex_uri, $uri, $match))
{
continue;
}
else
{
foreach ($match as $key => $value)
{
if (is_int($key))
{
//removing preg_match digit keys
continue;
}
$params[$key] = $value;
}
//if no values are set, load default ones
foreach ($route as $key => $value)
{
if (!isset($params[$key]))
{
$params[$key] = $value;
}
}
break;
}
}
return $params;
}
private static function make_regex_uri($uri)
{
$reg_escape = '[.\\+*?[^\\]${}=!|]';
$expression = preg_replace('#'.$reg_escape.'#', '\\\\$0', $uri);
if (strpos($expression, '(') !== FALSE)
{
$expression = str_replace(array('(', ')'), array('(?:', ')?'), $expression);
}
$reg_segment = '[^/.,;?\n]++';
$expression = str_replace(array('<', '>'), array('(?P<', '>'.$reg_segment.')'), $expression);
return '#^'.$expression.'$#uD';
}
And $routes
$routes['password'] = array(
'password(/<hash>)',
'controller' => 'account',
'method' => 'password',
'hash' => ''
);
Comments:
Bootstrap
Class recieves the request and calls static Router::make_uri
that return the current URL
.$uri
is matched against preset array of $routes
. If the regexp of any route matches the current URL
the passed values and default values (if passed value is not set) are added to $params
that will are returned on successful regexp match.$params
now has all the parameters defined in the matching route - Controller
, Method
and extra params, for example, the $hash
.The respective call is made (if class and method exists)
Class Account_Controller
{
public function password($hash)
{
echo $hash;
}
}
Upvotes: 2