Django-like URL Routing for PHP
I am looking for a way to provide URL routing similar to one in Django. I looked at a lot of resources online & I liked cobweb but the problem is I dont want to 开发者_如何转开发use the entire framework I just want to use the URL rerouting logic/code. Is there a good resource for just Django-like URL routing logic?
What you are looking for is a microframework. It's basically just the routing layer of a framework. There are several of these available. These looked interesting to me:
- Limonade
- Glue
- Slim
- Breeze
The one that really blew my mind though was Silex, which is based on Symfony2 and requires PHP 5.3.
I had looked at Limonade micro PHP framework's URL routing posted a reply using limonade codebase. However I went back and looked at cobweb code and it is sort of pretty and nice. So I just took Cobweb's URL routing and refactored that in OOP PHP5.x to use in my own code.
for comparison see:- http://docs.djangoproject.com/en/dev/topics/http/urls/ vs. http://www.limonade-php.net/README.htm
The usual disclaimers are in place
1) I have not tested much!
2) It may not have all of Django's URL routing capabilities
<?php
/**
*
* @author rajeev jha (jha dot rajeev at gmail)
* Django style URL routing.
* Pattern matching logic of this code is copied from cobweb framework
* @see http://code.google.com/p/cobweb/source/browse/trunk/dispatch/url_resolver.class.php
*
*/
class Gloo_Core_Router {
private $table ;
function __construct() {
//initialize routing table
$this->table = new Gloo_Core_RoutingTable();
}
function getRoute($path,$domain=NULL){
if(empty($path)) {
$message = sprintf("Please supply a valid path to match :: got [%s] ", $path);
trigger_error($message,E_USER_ERROR);
}
//all rules for this domain
$rules = $this->table->getRules($domain);
$route = NULL ;
if($path == '/')
$route = $this->matchHome($rules);
else
$route = $this->match($rules,$path);
return $route ;
}
private function matchHome($rules) {
$route = NULL ;
foreach($rules as $rule) {
if($rule["pattern"] == '/') {
$route = $this->createRoute($rule,array());
}
}
return $route ;
}
private function match($rules,$path) {
$path = ltrim($path, '/');
$matches = array();
$route = NULL ;
foreach($rules as $rule) {
if(preg_match($this->patternize($rule["pattern"]),$path,$matches) != 0 ) {
//match happened
$matches = $this->sanitizeMatches($matches);
$route = $this->createRoute($rule,$matches);
}
}
return $route ;
}
private function createRoute($rule,$matches) {
$route = $rule ;
//add parameters
$route["params"] = $matches ;
return $route ;
}
private function sanitizeMatches($matches){
//discard the first one
if (count($matches) >= 1)
$matches = array_splice($matches,1);
$unset_next = false;
//group name match will create a string key as well as int key
// like match["token"] = soemthing and match[1] = something
// remove int key when string key is present for same value
foreach ($matches as $key => $value) {
if (is_string($key)){
$unset_next = true;
} else if (is_int($key) && $unset_next) {
unset($matches[$key]);
$unset_next = false;
}
}
return array_merge($matches);
}
private function patternize($pattern) {
//http://www.php.net/manual/en/reference.pcre.pattern.modifiers.php
//treat pattern as UTF-8
return '{'.$pattern.'}u' ;
}
}
?>
<?php
/**
*
* @author rajeev jha (jha dot rajeev at gmail)
* Django style URL routing.
* URL routing table
*
*/
class Gloo_Core_RoutingTable {
private $rules ;
private $splrules ;
const ANY_DOMAIN = '__ANY__' ;
function __construct() {
$this->rules = array();
$this->splrules = array();
//@todo inject from outside
$this->createRule(self::ANY_DOMAIN, '/', 'Gloo_Controller_Home');
//match alphanumeric + dashes
//a pcre word (\w) does not contain dashes
$this->createRule(self::ANY_DOMAIN, '^(?P<token>[-\w]+)$','Gloo_Controller_Post');
$this->createRule(self::ANY_DOMAIN, '^page/(?P<pagenum>\d+)$','Gloo_Controller_Home');
$this->createRule(self::ANY_DOMAIN, '^(?P<token>\w+)/page/(?P<pagenum>\d+)$','Gloo_Controller_Post');
$this->createRule(self::ANY_DOMAIN, '^category/(?P<name>\w+)$','Gloo_Controller_Category');
$this->createRule(self::ANY_DOMAIN, '^category/(?P<name>\w+)/page/(?P<pagenum>\d+)$','Gloo_Controller_Category');
//special rules
$this->createRule('www.test1.com','^(?P<token>\w+)$','Gloo_Controller_File', array("template" => "post.php"));
}
function createRule($domain,$pattern,$action,$options=NULL) {
if(empty($domain)) {
trigger_error("No domain supplied for rule" ,E_USER_ERROR);
}
$rule = array();
$rule["pattern"] = $pattern;
$rule["action"] = $action ;
//Add options
if(is_null($options))
$rule["options"] = array();
else
$rule["options"] = $options ;
$rule["domain"] = $domain ;
//add to generic or domain specific rules
if($domain == self::ANY_DOMAIN)
$this->rules[] = $rule ;
else
$this->splrules[$domain][] = $rule ;
}
function getRules($domain) {
if(empty($domain))
return $this->rules ;
//valid domain - rules as well
// add to existing rules
if(array_key_exists($domain,$this->splrules)) {
$splrules = $this->splrules[$domain];
$rules = array_merge($this->rules,$splrules);
return $rules ;
} else {
return $this->rules ;
}
}
}
?>
Now, You can add any rules to Routing table initialization. depending on what domain + "path" you supply to router you will get back the "controller" string together with the "named group" parameters and other matched parameters. I am not including class instantiation logic for "controller" strings because 1) that would be complicated 2) If your intention is to just get a Django like URL routing then this much is good enough.
References
Limonade code is here:- https://github.com/sofadesign/limonade/blob/master/lib/limonade.php
Cobweb code is here:- http://code.google.com/p/cobweb/source/browse/trunk/dispatch/url_resolver.class.php
- My Code is here:- https://code.google.com/p/webgloo/source/browse/trunk/php/lib/Gloo/Core/Router.php
- Old version of my code :- what I edited here - was based on limonade and it was quite brain damaged. Feel free to look at Limonade link above. I am removing it.
Usage
$router = new Gloo_Core_Router();
$path = 'category/news/page/3' ;
printf("Now matching path [%s]" , $path);
$route = $router->getRoute($path);
print_r($route);
$path = '/category/Health/page' ;
printf("Now matching path [%s]" , $path);
$route = $router->getRoute($path);
print_r($route);
You have to change the class names and include dependencies but hey, you are a programmer ;D
I don't think is exactly the same, but the Zend Framework has pretty descent routing functionality that you might want to look at. The Zend Framework is kind of a component-based framework that doesn't force you into using the whole thing. But I think if you use the routing functionality you might need to also use their controller mechanism as well since the router is built on top.
You could look at CakePHP. I don't know how easy it would be to pull the URL routing logic away from the rest of the framework.
I developed a similar framework with codeigniter and django resources, http://williamborba.github.io/willer/quick_start/
It is still beta, but I am gradually implementing and improving the URL.php file is very similar to URLS.py django.
Another highlight are the models and the ORM much like django, but with something similar to the codeigniter active record.
精彩评论