简化前端控制器路由正则表达式

时间:2012-11-06 00:02:01

标签: php regex routes front-controller

我在制作自己的一个正则表达式时遇到很多困难以满足我的需求。 这是一个我试图不使用框架的应用程序,除了Doctrine,但在这种情况下并不重要。我正在使用Front Controller模式,并使用注释将路由映射到控制器的方法。

像:

/**
 * @GetMethod
 * @Route(name=index)
 * @return UserProfileDto
 */
public function getUserProfile();

任何人都可以帮助制作一个正则表达式以匹配我需要的所有内容吗?

规则是:

  • 必填:

    • 控制器(/控制器,网址上的第一项)
    • 类型(.json,.xml,.html,...)
  • 可选:

    • 动作(/控制器/动作,网址上的第二项)
    • 参数(动作和类型之间的所有内容,/ console / action / param1 / param2 / param3.type)

这是我设法做的事情:

<?php
header("content-type: text/plain");
// example url access: /profile/edit/23.html, /profile/delete/2.json, /profile.xml, /user/list.xml
$string = $_GET['u'];

$matches = array();

$objRoute = new stdClass();

preg_match_all("~^/(?P<controller>[^/\\.]+)~", $string, $matches);

if ($matches && $matches['controller']) {
    $objRoute->controller = $matches['controller'][0];
} else {
    $objRoute->controller = "index";
}

preg_match_all("~^/$objRoute->controller/(?P<action>[^/\\.]+)~", $string, $matches);

if ($matches && $matches['action']) { 
    $objRoute->action = $matches['action'][0];

    preg_match_all("~^/$objRoute->controller/{$objRoute->action}(?:/[^\\.]+)?\\.(?P<type>.+)$~", $string, $matches);

} else {

    preg_match_all("~^/$objRoute->controller\\.(?P<type>.+)$~", $string, $matches);
    $objRoute->action = "index";
    $objRoute->parameters = null;
}

if ($matches && $matches['type']) {
    $objRoute->type = $matches['type'][0];

    preg_match_all("~^/$objRoute->controller/{$objRoute->action}(?:/(?P<parameters>[^\\.]+))?\\.{$objRoute->type}$~", $string, $matches);
    if ($matches && $matches['parameters'] && $matches['parameters'][0]) {
        $objRoute->parameters = explode("/",$matches['parameters'][0]);
    } else {
        $objRoute->parameters = null;
    }

} else {
    die("Bad Request, no reponse type provided");
}
// "advanced" example method route @Route(name=edit/{id})
$route = "edit/{id}";

$route = preg_replace("~\\{([^\\}]+)\\}~", '(?P<' . '${1}' . '>[^/]+)', $route);

$routeTo = $objRoute->action . "/" . implode("/",$objRoute->parameters);

if ($objRoute->parameters && count($objRoute->parameters)) {
    preg_match_all("~^$route$~", $routeTo , $matches);
} 

foreach($matches as $idx => $match) {
    if ($match && $match[0] && !is_numeric($idx)) {
        $objRoute->{$idx} = $match[0];
    }
}

print_r($objRoute);

?>

2 个答案:

答案 0 :(得分:3)

我会使用单个正则表达式来验证/匹配整个路由字符串,并从返回的$ matches数组中提取控制器,操作,参数和类型。之后可以解析参数部分,其可以具有可变数量的部分。这是一个经过测试的PHP脚本,带有注释的正则表达式,应该是一个非常好的起点:

<?php // test.php Rev:20121105_1900
$route = '/controller/action/param1/param2/param3.html';
$re = '%
    # Parse controller, action, params and type from route.
    ^                    # Anchor to start of string.
    /                    # $controller prefix (required).
    (?P<controller>      # $controller (required).
      [^/\\\\.]+         # Value = one or more non-/\..
    )                    # $controller (required).
    (?:                  # Action is optional.
      /                  # $action prefix.
      (?P<action>        # $action.
        [^/\\\\.]+       # Value = one or more non-/\..
      )                  # $action.
    )?                   # Action is optional.
    (?:                  # Parameters are optional.
      (?P<params>        # $params.
        (?:              # One or more parameters
          /              # Params separated by a /.
          [^/\\\\.]+     # Value = one or more non-/\..
        )+               # One or more parameters
      )                  # $params.
    )?                   # Parameters are optional.
    \.                   # $type prefix (required).
    (?P<type>            # $type (required).
      [^/\\\\.]+         # Value = one or more non-/\..
    )                    # $type (required).
    $                    # Anchor to end of string.
    %x';
if (preg_match($re, $route, $matches)) {
    // Valid route string.
    printf("OK: \"%s\" is a valid route string.\n", $route);
    printf(     "  controller = \"%s\"\n", $matches['controller']);
    printf(     "  type       = \"%s\"\n", $matches['type']);
    if ($matches['action']) {
        printf( "  action     = \"%s\"\n", $matches['action']);
    } else {
        printf( "  action     = (none)\n");
    }
    if ($matches['params']) {
        printf(     "  params     = \"%s\"\n", $matches['params']);
        $params = preg_split('%/%', $matches['params'], -1, PREG_SPLIT_NO_EMPTY);
        for ($i = 0; $i < count($params); ++$i) {
            printf( "    p[%d]     = \"%s\"\n", $i+1, $params[$i]);
        }
    } else {
        printf( "  params     = (none)\n");
    }
} else {
    // Not a valid route string.
    printf("ERROR: \"%s\" is NOT a valid route string.\n", $route);
}
?>

输出:

OK: "/controller/action/param1/param2/param3.html" is a valid route string.
  controller = "controller"
  type       = "html"
  action     = "action"
  params     = "/param1/param2/param3"
    p[1]     = "param1"
    p[2]     = "param2"
    p[3]     = "param3"

答案 1 :(得分:0)

您可以使用explode()

,而不是使用正则表达式

示例:

$url = "/profile/edit/23.html";
$parsed = explode("/",$url);

现在$ url的每个元素都在解析的数组中:

$objRoute->controller = $parsed[1];
$objRoute->action = $parsed[2];
...

$解析的转储将为您提供:

"" // blank
"profile" // controller
"edit" // action
"23.html" // values