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:
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:
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:
checks if that matches the $reqUrl (see at the for loop above)
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.
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
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]);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With