Frage

I'm having a lot of trouble making one single regex of my own to match my needs. It's an application that I'm trying not to use a framework, except Doctrine, but in that case doesn't matter. I'm using Front Controller pattern and I mapping my routes to controller's methods using annotations.

Like:

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

Can anyone help to make one single regex to match everything I need?

The rules are:

  • Required:

    • Controller ( /controller, first item on the url )
    • Type (.json, .xml, .html, ...)
  • Optional:

    • Action ( /controller/action, second item on the url )
    • Parameters ( everything between action and type, /controller/action/param1/param2/param3.type )

Here's what I managed to do:

<?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);

?>
War es hilfreich?

Lösung

I would use a single regex to validate/match the whole route string and extract the controller, action, parameters and type from the returned $matches array. The parameters part, which can have a variable number of parts, is parsed afterward. Here is a tested PHP script wit a commented regex that should be a pretty good starting point:

<?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);
}
?>

Output:

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"

Andere Tipps

Instead of using regex you can use explode()

Example:

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

Now each element of your $url is in the parsed array:

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

a dump of $parsed will give you:

"" // blank
"profile" // controller
"edit" // action
"23.html" // values
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top