0

I've been working on implementing a simple PHP router and have managed to make it work with required parameters. However, I'm struggling to implement optional parameters but I failed. I'll provide the relevant code for clarity.

Firstly, this is my index.php:

<?php
$app->route->get("/user/:id/?post_id", [SiteController::class, "contact"]);
?>

In the above code, I've used a colon (:id) for required parameters and a question mark (?post_id) for optional parameters.

Secondly, here is my Router class:

class Router {
    public function resolve() {
        $method = $this->request->getMethod();
        $url = $this->request->getUrl();
        foreach ($this->router[$method] as $routeUrl => $target) {
            $pattern = preg_replace('/\/:([^\/]+)/', '/(?P<$1>[^/]+)', $routeUrl);
            if (preg_match('#^' . $pattern . '$#', $url, $matches)) {
                $params = array_filter($matches, 'is_string', ARRAY_FILTER_USE_KEY);
                call_user_func([new $target[0], $target[1]], ...array_values($params));
                exit();
            }
        }
        throw new \Exception();
    }
}

I need assistance in solving the mystery of implementing optional parameters. Any help would be greatly appreciated. Thank you!

1
  • 1
    Please read up on how to create a Minimal, Reproducible Example, because it's unclear how your two small piece of code hang together. Also, if you're using some kind of platform it is handy if you mention it. In short: Don't assume we know everything. Commented Oct 18, 2023 at 23:21

1 Answer 1

0

Here is a function that compiles a route path pattern into a regular expression with named capture groups. It uses a callback function to return different patterns depending on the indicator character.

function compileRoutePattern(string $route): string {
    // replace parameter syntax with named capture groups
    return '(^'.preg_replace_callback(
      '((?<prefix>^|/)(?:(?<indicator>[:?])(?<name>[\w\d]+)|[^/]+))',
      function($match) {
          return match ($match['indicator'] ?? '') {
               // mandatory parameter 
              ':' => sprintf('(?:%s(?<%s>[^/]+))', $match['prefix'], $match['name']),
               // optional parameter 
              '?' => sprintf('(?:%s(?<%s>[^/]+))?', $match['prefix'], $match['name']),
              // escape anything else
              default => preg_quote($match[0], '(')
          };
      },
      $route
    ).')i';
}

The next step would be to match a path and return only the named capture groups (not the numeric keys).

function matchRoute(string $path, string $route): ?array {
    // compile route into regex with named capture groups
    $pattern = compileRoutePattern($route);
    // match
    if (preg_match($pattern, $path, $match)) {
        // filter numeric keys from match
        return array_filter(
            $match, 
            fn($key) => is_string($key),
            ARRAY_FILTER_USE_KEY
        );   
    }
    return null;
}

Hint: I am using () as pattern delimiters to avoid conflicts. Think of them as group 0.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.