diff --git a/Route.php b/Route.php index 0539875..ab8a190 100644 --- a/Route.php +++ b/Route.php @@ -2,41 +2,51 @@ namespace WillySoft; +use Exception; + abstract class Route { static $prefix = ''; static $groups = [[]]; - static function match(string $methods, string $uri, callable $callback) + static function match(string $methods, string $url_path, callable $callback) { - if (!in_array( - $_SERVER['REQUEST_METHOD'], - array_map(strtoupper(...), explode('|', $methods)) - )) return; + if (!in_array( + $_SERVER['REQUEST_METHOD'], + array_map(strtoupper(...), explode('|', $methods)) + )) return; - $request_uri_parts = explode('/', urldecode( + $request_url_path_parts = explode('/', urldecode( strtok($_SERVER['REQUEST_URI'], '?') )); - $uri_parts = explode('/', self::$prefix . $uri); + $url_path_parts = explode('/', self::$prefix . $url_path); $callback_args = []; - for ($i = 0; $i < count($uri_parts); $i++) { - if ($uri_parts[$i] === '') + for ($i = 1; $i < count($url_path_parts); $i++) { + if ($url_path_parts[$i][0] !== '$') continue; - if ($uri_parts[$i][0] !== '$') - continue; - if (!isset($request_uri_parts[$i])) + if (!isset($request_url_path_parts[$i])) return; - if ( - $uri_parts[$i][-1] !== '?' - && $request_uri_parts[$i] === '' + + if ($url_path_parts[$i][-1] !== '?' + && $request_url_path_parts[$i] === '' ) return; - if ($request_uri_parts[$i] !== '') - array_push($callback_args, $request_uri_parts[$i]); - $request_uri_parts[$i] = $uri_parts[$i]; + if ($url_path_parts[$i][-1] === '?' + && $i !== (count($url_path_parts) - 1) + ) throw new Exception( + 'Only the last route parameter is allowed to be optional.' + ); + + if ($request_url_path_parts[$i] !== '') + array_push( + $callback_args, + htmlspecialchars($request_url_path_parts[$i]) + ); + + $request_url_path_parts[$i] = $url_path_parts[$i]; } if ( - implode('/', $uri_parts) - !== implode('/', $request_uri_parts) + implode('/', $url_path_parts) + !== implode('/', $request_url_path_parts) ) return; foreach (self::$groups as $middlewares) { @@ -48,22 +58,22 @@ abstract class Route die(); } - static function get(string $uri, callable $callback) - {self::match('get', $uri, $callback);} - static function post(string $uri, callable $callback) - {self::match('post', $uri, $callback);} - static function put(string $uri, callable $callback) - {self::match('put', $uri, $callback);} - static function patch(string $uri, callable $callback) - {self::match('patch', $uri, $callback);} - static function delete(string $uri, callable $callback) - {self::match('delete', $uri, $callback);} - static function options(string $uri, callable $callback) - {self::match('options', $uri, $callback);} - static function form(string $uri, callable $callback) - {self::match('get|post', $uri, $callback);} - static function any(string $uri, callable $callback) - {self::match('get|post|put|patch|delete|options', $uri, $callback);} + static function get(string $url_path, callable $callback) + {self::match('get', $url_path, $callback);} + static function post(string $url_path, callable $callback) + {self::match('post', $url_path, $callback);} + static function put(string $url_path, callable $callback) + {self::match('put', $url_path, $callback);} + static function patch(string $url_path, callable $callback) + {self::match('patch', $url_path, $callback);} + static function delete(string $url_path, callable $callback) + {self::match('delete', $url_path, $callback);} + static function options(string $url_path, callable $callback) + {self::match('options', $url_path, $callback);} + static function form(string $url_path, callable $callback) + {self::match('get|post', $url_path, $callback);} + static function any(string $url_path, callable $callback) + {self::match('get|post|put|patch|delete|options', $url_path, $callback);} static function use(callable $middleware) { diff --git a/example.php b/example.php index dc5da1f..afb010c 100644 --- a/example.php +++ b/example.php @@ -2,26 +2,24 @@ use WillySoft\Route as App; +// generated by composer, if you are not using composer include +// the file in a different manner +// require __DIR__ . '/edit/this/location/file.php'; require __DIR__ . '/../vendor/autoload.php'; -// middlewares provide a convenient mechanism for inspecting and -// filtering requests. you can imagine them as a series of layers -// that requests must pass through before they hit your application. -// a layer can be used for auth, rate limiting or anything really -App::use( - fn() => print('

Hello all routes!

')); - -// there are shorthands for methods you would expect such as +//----------------------------------------------------- +// Basic Routing +//----------------------------------------------------- +// there are shorthands for standard HTTP request methods such as // get|post|put|patch|delete|options -App::get('/', - fn() => print('homepage')); +App::get('/', function() { + ?> +

Homepage!

+

Welcome to the homepage

+ print('i match on any method')); - -// form is a shorthand that accepts GET and POST methods +// form is a shorthand that accepts GET and POST request methods App::form('/submit', fn() => print('i match on GET and POST methods')); @@ -30,24 +28,58 @@ App::form('/submit', App::match('get|post|put', '/match', fn() => print('i match on any method you like')); -// optional route parameters -App::get('/echo/$text?', - fn($text = 'You sent nothing') => print($text)); +// "any" is a shorthand allowing all HTTP request methods to pass +App::any('/example', + fn() => print('i match on any method')); -// required route parameters -App::get('/echo_must_supply_text/$text', +//----------------------------------------------------- +// Route Parameters +//----------------------------------------------------- +// capture segments of the URL_PATH within your route. for example, +// you may need to capture a users ID. you may do so by defining +// route parameters. +// parameters are injected into route callbacks based on their order. + +// a required parameter will only match when a value is supplied +App::get('/echo/$text', fn($text) => print($text)); +// optional parameter, only the last parameter can be optional +// or it will throw an exception +App::get('/echo_optional/$text?', + fn($text = 'You sent nothing') => print($text)); + +// crude friends list example +App::get('/user/$user_id/friends/$pagination?', function($user_id, $pagination = '0') { + ?> +

Showing friends for user:

+

Current page:

+ print('

Hello all routes!

')); + +//----------------------------------------------------- +// Route Groups +//----------------------------------------------------- // group together routes and middlewares. a prefix can be added to -// prefix each route in the group with a given PATH. the group will -// be skipped if the requested PATH does not begin with the one supplied. -// middlewares defined in here will only run on routes matched from -// within or any child groups +// prefix each route in the group with a given URL_PATH. the group +// will be skipped if the request URL_PATH does not start with the +// one supplied. middlewares defined in here will only run on routes +// matched from within or any child groups App::group('/test', function() { App::use( fn() => print('

Hello all routes matched within this or any child groups!

')); - // this will be matched as /test/ + // this will be seen as /test/ App::get('/', fn() => print('Testing 123')); });