diff --git a/.htaccess b/.htaccess index a491805..8ef0a37 100644 --- a/.htaccess +++ b/.htaccess @@ -4,6 +4,7 @@ DirectoryIndex index.php RewriteEngine on # set your rewrite base +# Edit this in your init method too if you script lives in a subfolder RewriteBase / # Deliver the folder or file directly if it exists on the server diff --git a/README.md b/README.md index 9510eb2..e390692 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,33 @@ # Simple PHP Router -Hey! This is a simple class that can handel the whole url routing for your project. - +Hey! This is a simple and small php router that can handel the whole url routing for your project. It utilizes RegExp and PHPs anonymous functions to create a lightweight and fast routing system. +The router supports dynamic path parameters, special 404 and 405 routes as well as verification of request methods like get, post, put, delete etc... +The codebase is very small and very easy to understand. So you can use it as boilerplate for a more complex router. Take a look in the index.php file. As you can see the ```Route::add()``` method is used to add new routes to your project. -The first argument takes the path segment. You can also use RegExp in there to parse out variables. -All matches will be pushed to the handler method. The handler is the second argument of the add function. +The first argument takes the path segment. You can also use RegExp in there to parse out variables. +The second argument will match the request method. The default method is 'get'. +All matching variables will be pushed to the handler method. -Example: +## Simple example: ``` -Route::add('/user/(.*)/edit',function($id){ - //Do something +include('Route.php'); + +Route::add('/user/([0-9]*)/edit',function($id){ echo 'Edit user with id '.$id.'
'; -}); +},'get'); + +Route::run('/'); ``` -## Testing -* Clone or download this repo -* Cd into the repo folder and run: -* ```php -S 0.0.0.0:8080``` -* Than open your Webbrowser and navigate to http://localhost:8080 +You will find a more complex example with a build in navigation in the index.php file. + +## Use a different basepath +If your script lives in a subfolder e.g. /api/v1 set this basepath in your run method: +```Route::run('/api/v1');``` +Do not forget to edit the basepath in .htaccess if you are on apache ## Something does not work? -* Dont forget to set the basepath in your index.php and .htaccess file. +* Dont forget to set the correct basepath as argument in your run method and in your .htaccess file. * Enable mod_rewrite in your apache settings diff --git a/Route.php b/Route.php index 08ec516..657c53c 100644 --- a/Route.php +++ b/Route.php @@ -3,84 +3,99 @@ class Route{ private static $routes = Array(); - private static $routes404 = Array(); - private static $path; - private static $basepath = '/'; + private static $pathNotFound = null; + private static $methodNotAllowed = null; + + public static function add($expression, $function, $method = 'get'){ + array_push(self::$routes,Array( + 'expression' => $expression, + 'function' => $function, + 'method' => $method + )); + } + + public static function pathNotFound($function){ + self::$pathNotFound = $function; + } + + public static function methodNotAllowed($function){ + self::$methodNotAllowed = $function; + } - public static function init($basepath = '/'){ - - self::$basepath = $basepath; - + public static function run($basepath = '/'){ + + // Parse current url $parsed_url = parse_url($_SERVER['REQUEST_URI']);//Parse Uri if(isset($parsed_url['path'])){ - self::$path = $parsed_url['path']; + $path = $parsed_url['path']; }else{ - self::$path = '/'; + $path = '/'; } - } - - public static function add($expression,$function){ - - array_push(self::$routes,Array( - 'expression'=>$expression, - 'function'=>$function - )); - - } - - public static function add404($function){ - - array_push(self::$routes404,$function); - - } - - public static function run(){ - - $route_found = false; + // Get current request method + $method = $_SERVER['REQUEST_METHOD']; + + $path_match_found = false; + + $route_match_found = false; foreach(self::$routes as $route){ - + + // If the method matches check the path + // Add basepath to matching string - if(self::$basepath!=''&&self::$basepath!='/'){ - $route['expression'] = '('.self::$basepath.')'.$route['expression']; - } - - // Add 'find string start' automatically - $route['expression'] = '^'.$route['expression']; + if($basepath!=''&&$basepath!='/'){ + $route['expression'] = '('.$basepath.')'.$route['expression']; + } + + // Add 'find string start' automatically + $route['expression'] = '^'.$route['expression']; - // Add 'find string end' automatically - $route['expression'] = $route['expression'].'$'; + // Add 'find string end' automatically + $route['expression'] = $route['expression'].'$'; // echo $route['expression'].'
'; - // Check match - if(preg_match('#'.$route['expression'].'#',self::$path,$matches)){ - - array_shift($matches);// Always remove first element. This contains the whole string - - if(self::$basepath!=''&&self::$basepath!='/'){ - - array_shift($matches);// Remove Basepath - - } - - call_user_func_array($route['function'], $matches); - - $route_found = true; - + // Check path match + if(preg_match('#'.$route['expression'].'#',$path,$matches)){ + + $path_match_found = true; + + // Check method match + if(strtolower($method) == strtolower($route['method'])){ + + array_shift($matches);// Always remove first element. This contains the whole string + + if($basepath!=''&&$basepath!='/'){ + array_shift($matches);// Remove basepath + } + + call_user_func_array($route['function'], $matches); + + $route_match_found = true; + + // Do not check other routes + break; + } } - } - if(!$route_found){ + // No matching route was found + if(!$route_match_found){ - foreach(self::$routes404 as $route404){ - - call_user_func_array($route404, Array(self::$path)); - - } + // But a matching path exists + if($path_match_found){ + header("HTTP/1.0 405 Method Not Allowed"); + if(self::$methodNotAllowed){ + call_user_func_array(self::$methodNotAllowed, Array($path,$method)); + } + }else{ + header("HTTP/1.0 404 Not Found"); + if(self::$pathNotFound){ + call_user_func_array(self::$pathNotFound, Array($path)); + } + } } diff --git a/index.php b/index.php index f10afc2..4fc1836 100644 --- a/index.php +++ b/index.php @@ -1,76 +1,4 @@ -'; -}); - -// Accept only numbers as the second parameter. Other characters will result in a 404 -Route::add('/foo/([0-9]*)/bar',function($var1){ - // Do something - echo $var1.' is a great number!'; -}); - -// Long route example -Route::add('/foo/bar/foo/bar',function(){ - // Do something - echo 'hehe :-)
'; -}); - -// Crazy route with parameters (Will be triggered on the route pattern above too because it matches too) -Route::add('/(.*)/(.*)/(.*)/(.*)',function($var1,$var2,$var3,$var4){ - // Do something - echo 'You have entered: '.$var1.' / '.$var2.' / '.$var3.' / '.$var4.'
'; -}); - -// Add a 404 not found route -Route::add404(function($url){ - - // Send 404 Header - header("HTTP/1.0 404 Not Found"); - echo '404 :-(
'; - echo $url.' not found!'; - -}); - -// Check if any of the defined routes will match and execute them -Route::run(); - -?> - -
Navigation: + +'; +},'get'); + +// Post route example +Route::add('/contact-form',function(){ + echo 'Hey! The form has been sent:
'; + print_r($_POST); +},'post'); + +// Route with regexp parameter +// Be aware that (.*) will match / (slash) too. For example: /user/foo/bar/edit +// Also users could inject mysql-code or other untrusted data if you use (.*) +// You should better use a saver expression like /user/([0-9]*)/edit or /user/([A-Za-z]*)/edit +Route::add('/user/(.*)/edit',function($id){ + echo 'Edit user with id '.$id.'
'; +}); + +// Accept only numbers as parameter. Other characters will result in a 404 error +Route::add('/foo/([0-9]*)/bar',function($var1){ + echo $var1.' is a great number!'; +}); + +// Crazy route with parameters +Route::add('/(.*)/(.*)/(.*)/(.*)',function($var1,$var2,$var3,$var4){ + echo 'This is the first match: '.$var1.' / '.$var2.' / '.$var3.' / '.$var4.'
'; +}); + +// Long route example +// This route gets never triggered because the route before matches too +Route::add('/foo/bar/foo/bar',function(){ + echo 'This is the second match
'; +}); + +// 405 test +Route::add('/this-route-is-defined',function(){ + echo 'You need to patch this route to see this content'; +},'patch'); + +// Add a 404 not found route +Route::pathNotFound(function($path){ + echo 'Error 404 :-(
'; + echo 'The requested path "'.$path.'" was not found!'; +}); + +// Add a 405 method not allowed route +Route::methodNotAllowed(function($path, $method){ + echo 'Error 405 :-(
'; + echo 'The requested path "'.$path.'" exists. But the request method "'.$method.'" is not allowed on this path!'; +}); + +// Run the Router with the given Basepath +// If your script lives in the web root folder use a / or leave it empty +Route::run('/'); + +// If your script lives in a subfolder you can use the following example +// Do not forget to edit the basepath in .htaccess if you are on apache +// Route::run('/api/v1'); + +?> \ No newline at end of file