From 807596ce70e23671c63ab0c3ef34b582c5a6c37e Mon Sep 17 00:00:00 2001 From: dconco Date: Thu, 26 Dec 2024 15:26:43 +0100 Subject: [PATCH] implemented case insensitive function for route --- Router/MapRoute.php | 36 +++- Router/Route.php | 12 ++ src/Foundation/Application.php | 4 +- src/Foundation/Render.php | 15 +- src/Traits/Resources/RouteResources.php | 8 - .../Exception/InvalidTypesException.php | 2 +- src/Utils/Routes/StrictTypes.php | 159 ++++++++++-------- tests/manualTests/Router/RouteTest.php | 10 +- 8 files changed, 148 insertions(+), 98 deletions(-) diff --git a/Router/MapRoute.php b/Router/MapRoute.php index 83e420f..9de010c 100644 --- a/Router/MapRoute.php +++ b/Router/MapRoute.php @@ -64,14 +64,13 @@ public function match(string $method, string|array $route): bool|array * | $_REQUEST['uri'] will be empty if req uri is / * ---------------------------------------------- */ - self::$request_uri = strtolower( - preg_replace("/(^\/)|(\/$)/", '', Application::$request_uri), - ); + self::$request_uri = + preg_replace("/(^\/)|(\/$)/", '', Application::$request_uri); self::$request_uri = empty(self::$request_uri) ? '/' : self::$request_uri; self::$route = is_array($route) ? $route - : strtolower(preg_replace("/(^\/)|(\/$)/", '', $route)); + : preg_replace("/(^\/)|(\/$)/", '', $route); // Firstly, resolve route with pattern if (is_array(self::$route)) @@ -191,9 +190,15 @@ public function match(string $method, string|array $route): bool|array * ----------------------------------- */ $reqUri = str_replace('/', '\\/', $reqUri); + $route = self::$route; + + if (Application::$caseInSensitive === true) { + $reqUri = strtolower($reqUri); + $route = strtolower($route); + } // now matching route with regex - if (preg_match("/$reqUri/", self::$route . '$')) + if (preg_match("/$reqUri/", $route . '$')) { // checks if the requested method is of the given route if ( @@ -247,6 +252,7 @@ private function match_routing (): bool|array { $uri = []; $str_route = ''; + $request_uri = self::$request_uri; if (is_array(self::$route)) { @@ -255,14 +261,20 @@ private function match_routing (): bool|array $each_route = preg_replace("/(^\/)|(\/$)/", '', self::$route[$i]); empty($each_route) - ? array_push($uri, strtolower('/')) - : array_push($uri, strtolower($each_route)); + ? array_push($uri, '/') + : array_push($uri, Application::$caseInSensitive === true ? strtolower($each_route) : $each_route); } } else { $str_route = empty(self::$route) ? '/' : self::$route; } + + + if (Application::$caseInSensitive === true) { + $request_uri = strtolower($request_uri); + $str_route = strtolower($str_route); + } if ( in_array(self::$request_uri, $uri) || @@ -329,9 +341,15 @@ private function pattern (): array|bool */ private function validatePattern (string $pattern): array|bool { + $request_uri = self::$request_uri; $pattern = preg_replace("/(^\/)|(\/$)/", '', trim(substr($pattern, 8))); - if (fnmatch($pattern, self::$request_uri)) + if (Application::$caseInSensitive === true) { + $request_uri = strtolower($request_uri); + $pattern = strtolower($pattern); + } + + if (fnmatch($pattern, $request_uri)) { if ( !in_array($_SERVER['REQUEST_METHOD'], self::$method) && @@ -349,4 +367,4 @@ private function validatePattern (string $pattern): array|bool } return false; } -} +} \ No newline at end of file diff --git a/Router/Route.php b/Router/Route.php index bcd2d12..d790b71 100644 --- a/Router/Route.php +++ b/Router/Route.php @@ -51,6 +51,8 @@ class Route extends Controller implements RouteInterface private ?array $mapRoute = null; + private bool $caseSensitive = false; + private ?Closure $handleInvalidParameterType = null; private static array $routes; @@ -224,6 +226,12 @@ public function handleInvalidParameterType (Closure $closure): self $this->handleInvalidParameterType = $closure; return $this; } + + public function caseSensitive (): self + { + $this->caseSensitive = true; + return $this; + } /** * Applies Authentication Guard to the current route. @@ -404,6 +412,10 @@ public function __destruct () $route_index = end(self::$route); $route_index = is_array($route_index) ? $route_index[0] : $route_index; + $GLOBALS['__registered_routes'][$route_index][ + 'caseSensitive' + ] = $this->caseSensitive; + if (self::$map !== null) { $GLOBALS['__registered_routes'][$route_index]['map'] = self::$map; diff --git a/src/Foundation/Application.php b/src/Foundation/Application.php index 597b4c8..a1efc04 100644 --- a/src/Foundation/Application.php +++ b/src/Foundation/Application.php @@ -41,7 +41,7 @@ class Application extends Controller implements ApplicationInterface /** * The version of the PhpSlides application. */ - public const PHPSLIDES_VERSION = '1.4.3'; + public const PHPSLIDES_VERSION = '1.4.4'; /** * @var string $REMOTE_ADDR The remote address of the client making the request. @@ -89,6 +89,8 @@ class Application extends Controller implements ApplicationInterface * The directory path for script resources (e.g., JavaScript files). */ public static string $scriptsDir; + + public static bool $caseInSensitive = true; public static ?Closure $handleInvalidParameterType; diff --git a/src/Foundation/Render.php b/src/Foundation/Render.php index 88438c2..b1aa6ee 100644 --- a/src/Foundation/Render.php +++ b/src/Foundation/Render.php @@ -33,8 +33,9 @@ public static function WebRoute () foreach ($reg_route as $route) { - self::$handleInvalidParameterType = - $route['handleInvalidParameterType'] ?? null; + $caseSensitive = $route['caseSensitive']; + $handleInvalidParameterType = $route['handleInvalidParameterType'] ?? null; + self::$redirect = $route['redirect'] ?? null; self::$method = $route['method'] ?? null; self::$guards = $route['guards'] ?? null; @@ -45,14 +46,8 @@ public static function WebRoute () self::$use = $route['use'] ?? null; self::$map = $route['map'] ?? null; - if (self::$handleInvalidParameterType) - { - self::__handleInvalidParameterType(); - } - else - { - Application::$handleInvalidParameterType = null; - } + Application::$handleInvalidParameterType = $handleInvalidParameterType; + Application::$caseInSensitive = !$caseSensitive; if (self::$map) { diff --git a/src/Traits/Resources/RouteResources.php b/src/Traits/Resources/RouteResources.php index b443c5a..07e04a0 100644 --- a/src/Traits/Resources/RouteResources.php +++ b/src/Traits/Resources/RouteResources.php @@ -30,8 +30,6 @@ trait RouteResources protected static ?string $file = null; - protected static ?Closure $handleInvalidParameterType = null; - protected static array|bool $map_info = false; /** @@ -73,12 +71,6 @@ protected static function __map (): void } } - protected static function __handleInvalidParameterType (): void - { - Application::$handleInvalidParameterType = - self::$handleInvalidParameterType; - } - protected static function __any (?Request $request = null): void { $route = self::$any['route'] ?? self::$method['route']; diff --git a/src/Utils/Routes/Exception/InvalidTypesException.php b/src/Utils/Routes/Exception/InvalidTypesException.php index 23f23d5..769a853 100644 --- a/src/Utils/Routes/Exception/InvalidTypesException.php +++ b/src/Utils/Routes/Exception/InvalidTypesException.php @@ -106,7 +106,7 @@ public static function catchInvalidParameterTypes (array $typeRequested, string $requested = implode(', ', $typeRequested); $requested = preg_replace('/<[^<>]*>/', '', $requested); - return new self(htmlspecialchars("Invalid request parameter type. {{$requested}} requested, but got {{$typeGotten}}")); + return new self(htmlspecialchars("Invalid request parameter type: Expected {{$requested}}, but received {{$typeGotten}}.")); } else { diff --git a/src/Utils/Routes/StrictTypes.php b/src/Utils/Routes/StrictTypes.php index ceab531..ce1ba3e 100644 --- a/src/Utils/Routes/StrictTypes.php +++ b/src/Utils/Routes/StrictTypes.php @@ -21,23 +21,20 @@ trait StrictTypes * @param array $haystack The array of types to match against. * @return bool Returns true if the type of the string matches any type in the array, false otherwise. */ - protected static function matchType (string $needle, array $haystack): bool + protected static function matchType(string $needle, array $haystack): bool { $typeOfNeedle = self::typeOfString($needle); - foreach ($haystack as $type) - { + foreach ($haystack as $type) { $type = strtoupper(trim($type)); $type = $type === 'INTEGER' ? 'INT' : $type; $type = $type === 'BOOLEAN' ? 'BOOL' : $type; - if (self::matches($needle, $type)) - { + if (self::matches($needle, $type)) { return true; } - if (strtoupper($type) === $typeOfNeedle) - { + if (strtoupper($type) === $typeOfNeedle) { return true; } } @@ -45,7 +42,6 @@ protected static function matchType (string $needle, array $haystack): bool return false; } - /** * Matches the given string against a list of types and returns the value * cast to the matched type. @@ -55,30 +51,30 @@ protected static function matchType (string $needle, array $haystack): bool * @return int|bool|float|array|string The value cast to the matched type. * @throws InvalidTypesException If the type of the needle does not match any type in the haystack. */ - protected static function matchStrictType ( - string $needle, - array $haystack, + protected static function matchStrictType( + string $needle, + array $haystack, ): int|bool|float|array|string { - $types = array_map(fn ($t) => strtoupper($t), $haystack); + $types = array_map(fn($t) => strtoupper($t), $haystack); $typeOfNeedle = self::typeOfString($needle); - if (self::matchType($needle, $types)) - { - return match ($typeOfNeedle) - { - 'INT' => (int) $needle, - 'BOOL' => filter_var($needle, FILTER_VALIDATE_BOOLEAN), - 'FLOAT' => (float) $needle, - 'ARRAY' => json_decode($needle, true), - default => $needle, + if (self::matchType($needle, $types)) { + return match ($typeOfNeedle) { + 'INT' => (int) $needle, + 'BOOL' => filter_var($needle, FILTER_VALIDATE_BOOLEAN), + 'FLOAT' => (float) $needle, + 'ARRAY' => json_decode($needle, true), + default => $needle, }; } InvalidTypesException::catchInvalidStrictTypes($haystack); - throw InvalidTypesException::catchInvalidParameterTypes($types, $typeOfNeedle); + throw InvalidTypesException::catchInvalidParameterTypes( + $types, + $typeOfNeedle, + ); } - /** * Matches the type of the given needle against the specified haystack type. * @@ -90,7 +86,7 @@ protected static function matchStrictType ( * @return bool Returns true if the needle matches the haystack type, otherwise false. * @throws InvalidTypesException If the needle does not match the haystack type. */ - private static function matches (string $needle, string $haystack): bool + private static function matches(string $needle, string $haystack): bool { $typeOfNeedle = self::typeOfString((string) $needle); $typeOfNeedle2 = $typeOfNeedle; @@ -100,40 +96,46 @@ private static function matches (string $needle, string $haystack): bool * MATCH ARRAY RECURSIVELY */ if ( - preg_match('/ARRAY<(.+)>/', $haystack, $matches) && - $typeOfNeedle === 'ARRAY' - ) - { + preg_match('/ARRAY<(.+)>/', $haystack, $matches) && + $typeOfNeedle === 'ARRAY' + ) { $needle = json_decode($needle, true); $eachArrayTypes = preg_split('/,(?![^<]*>)/', $matches[1]); - if (!is_array($needle)) - { - throw new Exception("Invalid request parameter type. {ARRAY} requested, but got {{$typeOfNeedle}}"); + if (!is_array($needle)) { + $requested = implode(', ', $eachArrayTypes); + throw InvalidTypesException::catchInvalidParameterTypes( + $eachArrayTypes, + $typeOfNeedle + ); } - foreach ($eachArrayTypes as $key => $eachArrayType) - { - if (!isset($needle[$key])) - { - throw new Exception("Array index $key not found in the request parameter"); + foreach ($eachArrayTypes as $key => $eachArrayType) { + $eachTypes = preg_split( + '/\|(?![^<]*>)/', + trim(strtoupper($eachArrayType)), + ); + + if (!isset($needle[$key])) { + throw InvalidTypesException::catchInvalidParameterTypes( + $eachTypes, + 'NULL', + "Array index $key not found in the request parameter", + ); } $needle2 = is_array($needle[$key]) - ? json_encode($needle[$key]) - : (string) $needle[$key]; - - $eachTypes = preg_split('/\|(?![^<]*>)/', trim(strtoupper($eachArrayType))); + ? json_encode($needle[$key]) + : (string) $needle[$key]; $typeOfNeedle2 = self::typeOfString($needle2); - if (!self::matchType($needle2, $eachTypes)) - { + if (!self::matchType($needle2, $eachTypes)) { $requested = implode(', ', $eachTypes); InvalidTypesException::catchInvalidStrictTypes($eachTypes); throw InvalidTypesException::catchInvalidParameterTypes( - $eachTypes, - $typeOfNeedle2, - "Invalid request parameter type. {{$requested}} requested on array index $key, but got {{$typeOfNeedle2}}", + $eachTypes, + $typeOfNeedle2, + "Invalid request parameter type: Expected {{$requested}} at array index {{$key}}, but received {{$typeOfNeedle2}}.", ); } } @@ -143,16 +145,42 @@ private static function matches (string $needle, string $haystack): bool /** * MATCH INT */ - if (preg_match('/INT<(\d+)(?:,\s*(\d+))?>/', $haystack, $matches) && $typeOfNeedle === 'INT') - { + if ( + preg_match('/INT<(\d+)(?:,\s*(\d+))?>/', $haystack, $matches) && + $typeOfNeedle === 'INT' + ) { $min = (int) $matches[1]; $max = (int) $matches[2] ?? null; $needle = (int) $needle; - if ((!$max && $needle < $min) || $max && ($needle < $min || $needle > $max)) - { + if ( + (!$max && $needle < $min) || + ($max && ($needle < $min || $needle > $max)) + ) { $requested = !$max ? "INT min ($min)" : "INT min ($min), max($max)"; - throw new Exception("Invalid request parameter type. {{$requested}} requested, but got {{$needle}}"); + throw InvalidTypesException::catchInvalidParameterTypes( + [$requested], + (string) $needle, + ); + } + return true; + } + + /** + * MATCH ENUM TYPE + */ + if (preg_match('/ENUM<(.+)>/', $haystack, $matches)) { + $needle = strtoupper($needle); + $enum = array_map(fn($e) => trim($e), explode('|', $matches[1])); + + if (!in_array($needle, $enum)) { + $requested = implode(', ', $enum); + + throw InvalidTypesException::catchInvalidParameterTypes( + $enum, + $needle, + "Invalid request parameter type: Expected an enum of type {{$requested}}, but received {{$needle}}.", + ); } return true; } @@ -161,7 +189,6 @@ private static function matches (string $needle, string $haystack): bool return false; } - /** * Determines the type of a given string. * @@ -177,28 +204,28 @@ private static function matches (string $needle, string $haystack): bool * @param string $string The input string to be analyzed. * @return string The type of the input string. */ - protected static function typeOfString (string $string): string + protected static function typeOfString(string $string): string { $decoded = json_decode($string, false); - if (is_numeric($string)) - { + if (is_numeric($string)) { return strpos($string, '.') !== false ? 'FLOAT' : 'INT'; - } - elseif (filter_var($string, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) !== null) - { + } elseif ( + filter_var( + $string, + FILTER_VALIDATE_BOOLEAN, + FILTER_NULL_ON_FAILURE, + ) !== null + ) { return 'BOOL'; - } - elseif (json_last_error() === JSON_ERROR_NONE) - { - return match (gettype($decoded)) - { - 'object' => 'JSON', - 'array' => 'ARRAY', - default => 'STRING', + } elseif (json_last_error() === JSON_ERROR_NONE) { + return match (gettype($decoded)) { + 'object' => 'JSON', + 'array' => 'ARRAY', + default => 'STRING', }; } return 'STRING'; } -} \ No newline at end of file +} diff --git a/tests/manualTests/Router/RouteTest.php b/tests/manualTests/Router/RouteTest.php index b3d1a50..3b8c27c 100644 --- a/tests/manualTests/Router/RouteTest.php +++ b/tests/manualTests/Router/RouteTest.php @@ -18,14 +18,18 @@ Route::map( GET, - "$dir/user/{id: int<6, 10>|bool|array|bool>, string>}", + "$dir/User/{id: int<6, 10>|bool|array|bool>, string>}/{status: enum}", ) ->action(function (Request $req) { echo '
'; - return $req->urlParam('id'); + return $req->url(); }) ->route('/posts/{id: int}', function (Request $req, Closure $accept) { $accept('POST'); - }); + }) + ->handleInvalidParameterType(function ($type) { + return $type; + }) + ->caseSensitive(); Render::WebRoute();