Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Url routing regex PHP

Tags:

regex

php

routing

I am writing a class that handles routing of my PHP webservice but I need to correct the regex, and I want to know what would be the most effecient way to parse the url.

Example urls:

  • POST /users
  • GET /users
  • GET /users&limit=10&offset=0
  • GET /users/search&keyword=Richard
  • GET /users/15/posts/38

What I want to create in PHP for class is this:

$router = new Router();
$router->addRoute('POST', '/users', function(){});
$router->addRoute('GET', '/users/:uid/posts/:pid', function($uid, $pid){});
$target = $router->doRouting();

The target variable would now contain an array with:

  • method
  • url
  • callback method

This is what I got so far:

class Router{
    use Singleton;

    private $routes = [];
    private $routeCount = 0;

    public function addRoute($method, $url, $callback){
        $this->routes[] = ['method' => $method, 'url' => $url, 'callback' => $callback];
        $this->routeCount++;
    }

    public function doRouting(){
        $reqUrl = $_SERVER['REQUEST_URI'];
        $reqMet = $_SERVER['REQUEST_METHOD'];

        for($i = 0; $i < $this->routeCount; $i++){
            // Check if the url matches ...
            // Parse the arguments of the url ...
        }
    }
}

So I need a regex that first of all:

  1. /mainAction/:argumentName/secondaryAction/:secondaryActionName

checks if that matches the $reqUrl (see at the for loop above)

  1. Extracts the arguments, so we can use them in our callback function.

What I tried myself:

(code should be in the for loop @ doRouting function)

// Extract arguments ...
$this->routing[$i]['url'] = str_replace(':arg', '.+', $this->routing[$i]['url']);

// Does the url matches the routing url?
if(preg_match('#^' . $this->routes[$i]['url'] . '$#', $reqUrl)){
    return $this->routes[$i];
}

I really appreciate all help, thanks alot.

like image 413
onlineracoon Avatar asked Jul 30 '12 13:07

onlineracoon


2 Answers

this basicly works now.

public function doRouting(){
    // I used PATH_INFO instead of REQUEST_URI, because the 
    // application may not be in the root direcory
    // and we dont want stuff like ?var=value
    $reqUrl = $_SERVER['PATH_INFO'];
    $reqMet = $_SERVER['REQUEST_METHOD'];

    foreach($this->routes as  $route) {
        // convert urls like '/users/:uid/posts/:pid' to regular expression
        $pattern = "@^" . preg_replace('/\\\:[a-zA-Z0-9\_\-]+/', '([a-zA-Z0-9\-\_]+)', preg_quote($route['url'])) . "$@D";
        $matches = Array();
        // check if the current request matches the expression
        if($reqMet == $route['method'] && preg_match($pattern, $reqUrl, $matches)) {
            // remove the first match
            array_shift($matches);
            // call the callback with the matched positions as params
            return call_user_func_array($route['callback'], $matches);
        }
    }
}

PS: You dont need the $routeCount attribute

like image 179
MarcDefiant Avatar answered Nov 15 '22 17:11

MarcDefiant


Great answer @MarcDefiant. Cleanest PHP router I came across. Did a small modification to support regular expression as well. Not sure why you use preq_quote ?

Small todo would be to cast the array to a assoc array. E.g. replace ['0' => 1] with ['id' => 1]

function matchRoute($routes = [], $url = null, $method = 'GET')
{
    // I used PATH_INFO instead of REQUEST_URI, because the 
    // application may not be in the root direcory
    // and we dont want stuff like ?var=value
    $reqUrl = $url ?? $_SERVER['PATH_INFO'];
    $reqMet = $method ?? $_SERVER['REQUEST_METHOD'];

    $reqUrl = rtrim($reqUrl,"/");

    foreach ($routes as $route) {
        // convert urls like '/users/:uid/posts/:pid' to regular expression
        // $pattern = "@^" . preg_replace('/\\\:[a-zA-Z0-9\_\-]+/', '([a-zA-Z0-9\-\_]+)', preg_quote($route['url'])) . "$@D";
        $pattern = "@^" . preg_replace('/:[a-zA-Z0-9\_\-]+/', '([a-zA-Z0-9\-\_]+)', $route['url']) . "$@D";
        // echo $pattern."\n";
        $params = [];
        // check if the current request params the expression
        $match = preg_match($pattern, $reqUrl, $params);
        if ($reqMet == $route['method'] && $match) {
            // remove the first match
            array_shift($params);
            // call the callback with the matched positions as params
            // return call_user_func_array($route['callback'], $params);
            return [$route, $params];
        }
    }
    return [];
}


$match = matchRoute([
    [
        'method' => 'GET',
        'url' => '/:id',
        'callback' => function($req) {
            exit('Hello');
        }
    ],
    [
        'method' => 'GET',
        'url' => '/api/(.*)', // Match all /api/hello/test/...
        'callback' => function($req) {
            print_r($req);
            exit('cool');
        }
    ]
]);

list($route,$params) = $match;

call_user_func_array($route['callback'], [$params]);
like image 29
Jelle Hak Avatar answered Nov 15 '22 16:11

Jelle Hak