Route/Route.php
2025-03-26 20:09:26 +00:00

164 lines
5.4 KiB
PHP

<?php
namespace WillySoft;
use \Exception;
use \InvalidArgumentException;
/**
* Minimalistic Web Application Router
*/
abstract class Route
{
/**
* Used for group prefixes
*/
static string $prefix = '';
/**
* Stores the middleware callbacks in their respective group; each nesting represented by index
*/
static array $groups = [
// Default initialized group where all middlewares are added to if they are not inside any other groups
[
//fn() => print('this could be a middleware, and it would print on every single route!')
],
//[
// fn() => print('this is what it would look like if a new group was created and a middleware was
// added inside of it!')
//]
];
/**
* Get request path relative to the entrypoint of the application
*/
static function getRelativeRequestPath(): string
{
// try to save a negligible amount of performance by caching the computated result
static $request_url_path;
if (isset($request_url_path)) {
return $request_url_path;
}
$request_url_path = substr_replace(
urldecode(strtok($_SERVER['REQUEST_URI'], '?')), '', 0, strlen($_SERVER['SCRIPT_NAME'])
);
if ($request_url_path === '')
$request_url_path = '/';
return $request_url_path;
}
/**
* Run all middlewares then callback if supplied arguments correspond with the request
*/
static function match(string $methods, string $url_path, callable $callback): void
{
if (!in_array(
$_SERVER['REQUEST_METHOD'],
array_map(strtoupper(...), explode('|', $methods))
)) return;
$request_url_path_parts = explode('/', self::getRelativeRequestPath());
$url_path_parts = explode('/', self::$prefix . $url_path);
$callback_args = [];
for ($i = 1; $i < count($url_path_parts); $i++) {
if ($url_path_parts[$i] === '')
continue;
if ($url_path_parts[$i][0] !== '$')
continue;
if (!isset($request_url_path_parts[$i]))
return;
if ($url_path_parts[$i][-1] !== '?'
&& $request_url_path_parts[$i] === ''
) return;
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('/', $url_path_parts)
!== implode('/', $request_url_path_parts)
) return;
foreach (self::$groups as $middlewares) {
foreach ($middlewares as $middleware) {
$middleware();
}
}
$callback(...$callback_args);
die();
}
// Shorthand for self::match(...)
static function get(string $url_path, callable $callback): void
{self::match('get', $url_path, $callback);}
// Shorthand for self::match(...)
static function post(string $url_path, callable $callback): void
{self::match('post', $url_path, $callback);}
// Shorthand for self::match(...)
static function put(string $url_path, callable $callback): void
{self::match('put', $url_path, $callback);}
// Shorthand for self::match(...)
static function patch(string $url_path, callable $callback): void
{self::match('patch', $url_path, $callback);}
// Shorthand for self::match(...)
static function delete(string $url_path, callable $callback): void
{self::match('delete', $url_path, $callback);}
// Shorthand for self::match(...)
static function options(string $url_path, callable $callback): void
{self::match('options', $url_path, $callback);}
// Shorthand for self::match(...)
static function form(string $url_path, callable $callback): void
{self::match('get|post', $url_path, $callback);}
// Shorthand for self::match(...)
static function any(string $url_path, callable $callback): void
{self::match('get|post|put|patch|delete|options', $url_path, $callback);}
/**
* Adds middleware to its respective group
*/
static function use(callable $middleware): void
{
array_push(
self::$groups[array_key_last(self::$groups)],
$middleware
);
}
/**
* Create new route group
*
* @param string $prefix Optional prefix can be supplied; the group is ignored if getRelativeRequestPath
* does not correspond with the supplied value.
* @param callable $callback Required; the insides of the group
*/
static function group(string $prefix = '', ?callable $callback = null): void
{
if ($callback === null) {
throw new InvalidArgumentException('Argument callback must be of type callable.');
}
if (!str_starts_with(
self::getRelativeRequestPath(),
self::$prefix . $prefix
)) return;
self::$prefix = self::$prefix . $prefix;
array_push(self::$groups, []);
$callback();
array_pop(self::$groups);
self::$prefix = rtrim(self::$prefix, $prefix);
}
}