我一直很喜欢 Rails 风格的 url 样式,现在我在使用 CodeIgniter 框架开发东西,也想使用 Rails 那样的 url 样式。为了避免再一次发明轮子,我网上搜了下,发现很多人也有这个需求,而且也有了好几个相应的实现(一,二,三)。但是,这些实现要么配置麻烦,要么代码量太多改动太大,和 CodeIgniter 的 Write Less, Do More 的口号有点不协调。
所以,我只好自己发明一个轮子啦
“发明”完毕后,和其他实现对比了下,我自认我的实现比现有的我所能找到的实现都更简洁、逻辑清晰、容易配置、好用。不是我自夸,我自己用着觉得确实好用,觉得是个好东西(媳妇是别人的好,孩子是自己的好?:-) ),所以发出来给大家分享分享,欢迎大家指教。
先看一下怎么使用这个轮子:
routes.php 配置文件后面添加以下内容:
// 在 application/config/routes.php 后面(或合适位置)加上这一行
map_resources('users');
///////////////////////////////////////////////////////////////////////////////
// 如果你不需要自定义额外的 url 规则,请无视下面的代码。。。
///////////////////////////////////////////////////////////////////////////////
// custom action handles GET request
map_resources('GET', 'users/(:id)/custom_action', 'users/custom_action/$1');
// some other custom action mappings...
// custom action handles PUT request
//map_resources('PUT', 'users/(:id)/custom_action', 'sync_sessions/custom_action/$1');
// custom action handles all requests (GET, POST, PUT, DELETE)
//map_resources('users/(:id)/custom_action', 'sync_sessions/custom_action/$1');
// ...
然后 controller 里就可以像 Rails 一样啦(除了 new, 因为 php 语言的限制,没办法 ):
<?php
class Users extends Controller
{
function Users()
{
parent::Controller();
}
// GET /users
function index()
{
echo __METHOD__ . "<br>\n" . $_SERVER['REQUEST_METHOD'] . " /users\n";
}
// GET /users/12345
function show($id)
{
echo __METHOD__ . "<br>\n" . $_SERVER['REQUEST_METHOD'] . " /users/{$id}\n";
}
// GET /users/new
// I have to use new_form as method name because "new" is a keyword in php
function new_form()
{
echo __METHOD__ . "<br>\n" . $_SERVER['REQUEST_METHOD'] . " /users/new\n";
}
// GET /users/12345/edit
function edit($id)
{
echo __METHOD__ . "<br>\n" . $_SERVER['REQUEST_METHOD'] . " /users/{$id}/edit\n";
}
// POST /users
function create()
{
echo __METHOD__ . "<br>\n" . $_SERVER['REQUEST_METHOD'] . " /users\n";
}
// PUT /users/12345
function update($id)
{
echo __METHOD__ . "<br>\n" . $_SERVER['REQUEST_METHOD'] . " /users/{$id}\n";
}
// DELETE /users/12345
function delete($id)
{
echo __METHOD__ . "<br>\n" . $_SERVER['REQUEST_METHOD'] . " /users/{$id}\n";
}
// GET /users/12345/custom_action
function custom_action($id)
{
echo __METHOD__ . "<br>\n" . $_SERVER['REQUEST_METHOD'] . " /users/{$id}/custom_action\n";
}
}
看到这里如果你感兴趣的话,可以继续往下看怎么实现了。其实非常简单,只有一个 MY_Router.php 文件,不到 100 行的代码(不计算空行和注释的话)。安装也很简单:只需要把这个 MY_Router.php 放到 application/libraries 目录下就万事大吉了。 代码逻辑我就不一一解释了,里面有注释,逻辑也简单,相信每个人都能看懂。
<?php
/**
* RESTful-CodeIgniter
*
* A RESTful url router library for CodeIgniter
*
* @author ZHENJiNG LiANG (liangzhenjing_N_O_S_P_A_M@gmail.com)
* @license BSD license
* @link http://liang.eu/web-dev/php/restful-style-url-in-codeigniter
*/
///////////////////////////////////////////////////////////////////////////////
// NOTE:
// This Library added two macros:
// :id as [^/]+ in order to match non-numeric id field,
// :uuid to match UUID (aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee)
//
// :any and :num remain the same
// See code below for details
// You can change at will
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
// map_resources() --> Return all routes
// map_resources($controller_name) --> Map default restful routes
// map_resources($method, $pattern, $replace) --> Add a custom route
///////////////////////////////////////////////////////////////////////////
function map_resources()
{
static $routes = array();
$arg_num = func_num_args();
if(0 == $arg_num)
{
return $routes;
}
elseif(1 == $arg_num)
{
// map_resources($controller_name) --> return all routes
$controller = func_get_arg(0);
// Generate RESTful url match patterns
$routes['GET']["{$controller}"] = "{$controller}/index";
$routes['GET']["{$controller}/new"] = "{$controller}/new_form";
$routes['GET']["{$controller}/(:id)"] = "{$controller}/show/$1";
$routes['GET']["{$controller}/(:id)/edit"] = "{$controller}/edit/$1";
$routes['POST']["{$controller}"] = "{$controller}/create";
$routes['PUT' ]["{$controller}/(:id)"] = "{$controller}/update/$1";
$routes['DELETE']["{$controller}/(:id)"] = "{$controller}/delete/$1";
}
elseif(2 == $arg_num)
{
// Custom url routing
$args = func_get_args();
$pattern = $args[0];
$replace = $args[1];
$routes['GET'][$pattern] = $replace;
$routes['POST'][$pattern] = $replace;
$routes['PUT'][$pattern] = $replace;
$routes['POST'][$pattern] = $replace;
}
elseif(3 == $arg_num)
{
// Custom url routing
$args = func_get_args();
$method = $args[0];
$pattern = $args[1];
$replace = $args[2];
$routes[$method][$pattern] = $replace;
}
}
///////////////////////////////////////////////////////////////////////////
// RESTful style url router
///////////////////////////////////////////////////////////////////////////
class MY_Router extends CI_Router
{
function MY_Router()
{
// HACK: support overridding REQUEST_METHOD by posting a _method parameter
if($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_POST['_method']))
{
$_SERVER['REQUEST_METHOD'] = strtoupper($_POST['_method']);
}
elseif(isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']))
{
$_SERVER['REQUEST_METHOD'] = strtoupper($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']);
}
parent::CI_Router();
}
// Overrides CI_Router::_parse_routes.
// RESTful style url matching
function _parse_routes()
{
$rest_routes = map_resources();
// we do this for performence
if (empty($rest_routes) && count($this->routes) == 1)
{
$this->_set_request($this->uri->segments);
return;
}
// also this...
$uri = implode('/', $this->uri->segments);
if (isset($this->routes[$uri]))
{
$this->_set_request(explode('/', $this->routes[$uri]));
return;
}
// RESTful url matching...
$request_method = $_SERVER['REQUEST_METHOD'];
$routes = (isset($rest_routes[$request_method]) && $rest_routes[$request_method]) ? $rest_routes[$request_method] : array();
foreach($routes as $pattern => $replace)
{
$pattern = str_replace(':id', '[^/]+', $pattern); // use this to match non-numeric id field
$pattern = str_replace(':any', '.+', $pattern);
$pattern = str_replace(':num', '[0-9]+', $pattern);
$pattern = str_replace(':uuid', '[a-zA-Z0-9]{8}(-[a-zA-Z0-9]{4}){3}-[a-zA-Z0-9]{12}', $pattern);
// Does the RegEx match?
if (preg_match("#^{$pattern}$#", $uri))
{
// Do we have a back-reference?
if (strpos($replace, '$') !== FALSE && strpos($pattern, '(') !== FALSE)
{
$replace = preg_replace("#^{$pattern}$#", $replace, $uri);
}
// we are done
$this->_set_request(explode('/', $replace));
return;
}
}
// if non of the rules match, then go on...
parent::_parse_routes();
}
}
这就是所有的代码,我相信足够简洁、易用了。 测试程序我也提供了,在 github 上的restful-codeigniter 项目里有完整代码,使用 Git clone 一份下来就可以进行测试了。
如果发现什么不好的地方或者bug,或者有更好的实现方法,非常欢迎探讨指教。