<?php
namespace gnomephp\mvc\router;
/**
* Main Router entry point for GnomePHP.
*
* This router is pretty advanced and can handle very specific configuration to more relaxed configuration.
* @author peec
*
*/
class Router{
const REGEXP_LINE = '/\s+|(.*?)\s+(.*?)\s+(.*?)$/';
const EXTEND_TAG = '/\{extend=([\/a-zA-Z0-9_-]*)\}/';
/**
* List of Rule objects.
* @var array
*/
private $rules=array();
/**
* Copy of $_SERVER object.
* Notice: only PATH_INFO and REQUEST_METHOD is needed as keys.
* @var unknown_type
*/
private $server;
private $url;
/**
* Since its a quite costly operation to generate links based on url, we cache UrlLinks.
*
* @var array
*/
private $reverseRouteCache = array();
/**
* This script REQUEST_METHOD from the server array.
* Define array with the element or just pass $_SERVER to it.
*
* @param array $server The $_SERVER array.
*/
public function __construct($url, $server){
$this->server = $server;
$this->url = $url;
}
/**
* Sets new current url.
*/
public function setUrl($url){
$this->url = $url;
}
/**
* Gets the URL Path .
* eg. www.mysite.com/news/this-is-my-article?var=shownews
* will return /news/this-is-my-article
*/
protected function getUrl(){
return $this->url ? $this->url : '/';
}
/**
* Gets the client request type as integer.
* @see Router::requestToInt
*/
protected function getClientRequest(){
return isset($this->server['REQUEST_METHOD']) ? $this->requestToInt($this->server['REQUEST_METHOD']) : Rule::R_GET;
}
/**
* Adds a new rule to the router.
* @param Rule $rule
*/
public function addRule($rule){
$this->rules[] = $rule;
}
/**
* Parses a router string config.
* Example of string:
* -------
* # GET /sa Controller.method
* GET /sa/{<[0-9]+>sd}/{sd2}/? Controller.method($sd2, $sd)
* # Default route.
* * /{controller}/{method}/? $controller.$method()
* -------
* @param string $stringConfig
*/
public function parse($stringConfig){
$lines = explode("\n", $stringConfig);
foreach($lines as $line){
$line = trim($line);
$isComment = substr($line, 0, 1) == '#';
// Parser extends.
$parts = array();
if (!$isComment && preg_match(Router::EXTEND_TAG, $line, $parts)){
$extend = $parts[1];
if (substr($extend, 0, 5) == 'core/'){
$this->parse(file_get_contents(GNOME_FW_PATH . '/res/config/routes/'.substr($extend, 5).'.conf'));
}
}
$parts = array();
if (!$isComment && preg_match(Router::REGEXP_LINE, $line, $parts) ){
if (count($parts)==4){
$this->addRule(new Rule($this->requestToInt($parts[1]), $parts[2], $parts[3]));
}
}
}
}
/**
* Returns int of a valid / supported request type.
*
* @param string $request GET, POST, or * ( * means all requests )
* @throws RouterParseException
*/
protected function requestToInt($request){
switch(strtoupper($request)){
case 'GET':
return Rule::R_GET;
break;
case 'POST':
return Rule::R_POST;
break;
case '*':
return Rule::R_ALL;
break;
default:
throw new RouterParseException("Could not parse router string. Unable to solve $request to any of the valid request types.");
}
}
/**
* Gets the Controller, method and arguments as an array.
*/
public function route(){
foreach($this->rules as $rule){
if ($r = $rule->validate($this->getClientRequest(), $this->getUrl())){
return $r;
}
}
return null;
}
/**
* Can reverse engineer a specific $controller, $method and arguments pattern.
*
* Will return object of UrlString.
*
*
* @param string $controller
* @param string $method
* @param array $args
* @return gnomephp\mvc\string\UrlString
*/
public function reverse($controller, $method, $args=array()){
$uniqueID = md5($controller . $method . var_export($args, true));
// Does it exist ?
if (isset($this->reverseRouteCache[$uniqueID])) return $this->reverseRouteCache[$uniqueID];
// Go thru routes.
foreach($this->rules as $rule){
if ($url = $rule->reverse($controller, $method, $args)){
// Cache it!
$this->reverseRouteCache[$uniqueID] = $url;
// Return
return $this->reverseRouteCache[$uniqueID];
}
}
return false;
}
}