如何使用基于Regex的路由处理404?

时间:2010-07-14 16:55:32

标签: php regex routing controller routes

请考虑以下非常基本的“控制器”(为简单起见,在这种情况下的功能):

function Index() {
    var_dump(__FUNCTION__); // show the "Index" page
}

function Send($n) {
    var_dump(__FUNCTION__, func_get_args()); // placeholder controller
}

function Receive($n) {
    var_dump(__FUNCTION__, func_get_args()); // placeholder controller
}

function Not_Found() {
    var_dump(__FUNCTION__); // show a "404 - Not Found" page
}

以下基于正则表达式的Route()函数

function Route($route, $function = null)
{
    $result = rtrim(preg_replace('~/+~', '/', substr($_SERVER['PHP_SELF'], strlen($_SERVER['SCRIPT_NAME']))), '/');

    if (preg_match('~' . rtrim(str_replace(array(':any', ':num'), array('[^/]+', '[0-9]+'), $route), '/') . '$~i', $result, $matches) > 0)
    {
        exit(call_user_func_array($function, array_slice($matches, 1)));
    }

    return false;
}

现在我想将以下URL(忽略尾部斜杠)映射到相应的“控制器”:

/index.php -> Index()
/index.php/send/:NUM -> Send()
/index.php/receive/:NUM -> Receive()
/index.php/NON_EXISTENT -> Not_Found()

这是事情开始变得棘手的部分,我有两个问题我无法解决......我想我不是第一个遇到这个问题的人,所以那里的人应该有溶液


捕获404(已解决!)

我找不到区分对根(index.php)的请求和不应该存在的请求(index.php/notHere)的方法。我最终为URL提供了默认的index.php路由,否则该路由应该是404 - Not Found错误页面。 我该如何解决这个问题?

编辑 - 解决方案在我脑海中浮现:

Route('/send/(:num)', 'Send');
Route('/receive/(:num)', 'Receive');
Route('/:any', 'Not_Found'); // use :any here, see the problem bellow
Route('/', 'Index');

路线排序

如果我按照“逻辑”顺序设置路线,如下所示:

Route('/', 'Index');
Route('/send/(:num)', 'Send');
Route('/receive/(:num)', 'Receive');
Route(':any', 'Not_Found');

所有URL请求都由Index()控制器捕获,因为空的正则表达式(记住:尾随斜杠被忽略)匹配所有内容。但是,如果我以“hacky”顺序定义路线,如下所示:

Route('/send/(:num)', 'Send');
Route('/receive/(:num)', 'Receive');
Route('/:any', 'Not_Found');
Route('/', 'Index');

所有似乎按照应有的方式工作。 有解决此问题的优雅方法吗?

路由可能并不总是硬编码(从数据库或其他东西中提取),并且我需要确保它不会因为定义它们的顺序而忽略任何路由。 感谢任何帮助!

3 个答案:

答案 0 :(得分:1)

好吧,我知道有一种方法可以给猫皮肤,但为什么你会这样做呢?看起来像某些RoR方法可以使用mod_rewrite轻松处理

话虽这么说,我重写了你的路线功能,并且能够实现你的目标。请记住,我添加了另一个条件来直接捕获索引,因为你正在删除所有的/这就是为什么它匹配索引时你希望它匹配404.我还整合了4个Route()调用使用一个foreach()。

function Route()
{
        $result = rtrim(preg_replace('~/+~', '/', substr($_SERVER['PHP_SELF'], strlen($_SERVER['SCRIPT_NAME']))), '/');
        $matches = array();

        $routes = array(
                'Send'      => '/send/(:num)',
                'Receive'   => '/receive/(:num)',
                'Index'     => '/',
                'Not_Found' => null
        );

        foreach ($routes as $function => $route)
        {
                if (($route == '/' && $result == '')
                        || (preg_match('~' . rtrim(str_replace(array(':any', ':num'), array('[^/]+', '[0-9]+'), $route)) . '$~i', $result, $matches) > 0))
                {
                        exit(call_user_func_array($function, array_slice($matches, 1)));
                }
        }

        return false;
}

Route();

干杯!

答案 1 :(得分:1)

这是MVC webapps的一个常见问题,通常在它成为问题之前就已经解决了。

最简单,最通用的方法是使用例外。如果您没有给定参数的内容,则抛出PageNotFound异常。在应用程序的顶层,捕获所有异常,如此简化示例:

的index.php:

try {
    $controller->method($arg);
} catch (PageNotFound $e) {
    show404Page($e->getMessage());
} catch (Exception $e) {
    logFatalError($e->getMessage());
    show500Page();
}

Controller.php这样:

function method($arg) {
    $obj = findByID($arg);
    if (false === $obj) {
         throw new PageNotFound($arg);
    } else {
         ...
    }
}

可以通过对正则表达式进行排序来解决排序问题,以便首先匹配最具体的正则表达式,并且最后匹配最不具体的正则表达式。为此,请计算正则表达式中的路径separtors(即斜杠),不包括开头的路径分隔符。你会得到这个:

 Regex           Separators
 --------------------------
 /send/(:num)    1
 /send/8/(:num)  2
 /               0

按降序排序,然后处理。流程订单是:

  1. /发送/ 8 /(:NUM)
  2. /发送/(:NUM)
  3. /

答案 2 :(得分:0)

首先确定:

foo.com/index.php/more/info/to/follow 

完全有效,并且按照标准应该加载index.php并将$ _SERVER [PATH_INFO]设置为/ more / info / to / follow。这是CGI/1.1 standard。如果您希望服务器不执行PATH_INFO扩展,请在服务器设置中将其关闭。在apache下,它使用:

完成
AcceptPathInfo Off

如果你在Apache2下将它设置为Off ...它将发送一个404.

我不确定IIS标志是什么,但我认为你可以找到它。