diff --git a/SegmentRouter.php b/SegmentRouter.php index 7f8b70a..d5bf9d4 100644 --- a/SegmentRouter.php +++ b/SegmentRouter.php @@ -11,9 +11,12 @@ class SegmentRouter implements Router return str_starts_with($segment, ':') ? ':x' : $segment; }, explode('/', trim($route, '/'))); - // Push each segment into the routes array as a node + // Push each segment into the routes array as a node, except if this is the root node $node = &$this->routes; - foreach ($segments as $segment) $node = &$node[$segment]; + foreach ($segments as $segment) { + if ($segment === '') continue; + $node = &$node[$segment]; + } // Add the handler to the last node $node[$method] = $handler; @@ -23,44 +26,36 @@ class SegmentRouter implements Router public function lookup(string $method, string $uri): int|array { - // Expand the URI into segments - $uriSegments = explode('/', trim($uri, '/')); $node = $this->routes; + + if (isset($node[$uri])) { + if (isset($node[$uri][$method])) return [$node[$uri][$method], []]; + return 405; + } + + $uriSegments = explode('/', trim($uri, '/')); $params = []; - - // Traverse the routes array to find the handler + foreach ($uriSegments as $segment) { - // Check if the segment exists in the node if (isset($node[$segment])) { $node = $node[$segment]; - } else { - // Handle dynamic segments (starting with ":") - $dynamicSegment = null; - - // Loop through the node and find the first dynamic segment - foreach ($node as $k => $v) { - if (str_starts_with($k, ':')) { - $dynamicSegment = $k; - break; // Break early as we only need one match - } - } - - // If no dynamic segment was found, return 404 - if ($dynamicSegment === null) return 404; - - // Otherwise, store the parameter and move to the dynamic node - $params[] = $segment; - $node = $node[$dynamicSegment]; + continue; } + + if (isset($node[':x'])) { + $params[] = $segment; + $node = $node[':x']; + continue; + } + + return 404; } - - // Check if the HTTP method is supported + if (!isset($node[$method])) return 405; - // Return the handler and parameters - return [$node[$method] , $params]; + return [$node[$method], $params]; } - + public function clear(): Router { diff --git a/tests/storage/segment/blog.txt b/tests/storage/segment/blog.txt index 6c46c81..ce584f1 100644 --- a/tests/storage/segment/blog.txt +++ b/tests/storage/segment/blog.txt @@ -1,6 +1,5 @@ / -├── / -│ └── GET +├── GET ├── :x │ └── GET ├── tags diff --git a/tests/tools.php b/tests/tools.php index b2ad00e..c6b175b 100644 --- a/tests/tools.php +++ b/tests/tools.php @@ -107,32 +107,67 @@ function readAndAddRoutes(string $file, Router &$r): array } function runIterations(int $iterations, $r, array $routes) { - echo "Running $iterations iterations\n"; - $start = microtime(true); - $interval = $iterations / 10; + echo "\n🚀 Running $iterations iterations...\n"; + + $start = microtime(true); // start the timer + $reqs = 0; // track the timing of lookups + $longest = 0; // track the longest lookup time + $shortest = 0; // track the shortest lookup time + $longestRoute = ''; + $shortestRoute = ''; + for ($i = 0; $i < $iterations; $i++) { // pick a random route from the array [$method, $path] = $routes[array_rand($routes)]; - /* + // replace all :params/ with random values $uri = preg_replace_callback('/:(\w+)/', function($matches) { return rand(1, 100); }, $path); - */ - $uri = $path; - $start2 = microtime(true); - $res = $r->lookup($method, $uri); + + $start2 = microtime(true); // start the timer for the lookup + + $res = $r->lookup($method, $uri); // run the lookup + + $reqs += microtime(true) - $start2; // add this lookup time + if ($shortest == 0 || microtime(true) - $start2 < $shortest) { + $shortest = microtime(true) - $start2; // track the shortest lookup time + $shortestRoute = "$method $uri"; + } + if (microtime(true) - $start2 > $longest) { + $longest = microtime(true) - $start2; // track the longest lookup time + $longestRoute = "$method $uri"; + } + + // if any error was encountered, print it and exit if ($res === 404 || $res === 405) { echo Color::red("Failed to handle request.\n$method $res\n"."├─ URI: $uri\n└─ Path: $path\n"); echo Color::yellow("Completed $i iterations before failure.\n"); exit(1); } - if ($i !== 0 && $i % ($interval) === 0) echoMemoryAndTime($i, $start2); } + echo Color::blue("✔️ Done!")."\n"; + + // echo peak memory usage + echo "Peak memory: " . Color::magenta(round(memory_get_peak_usage() / 1024, 1) . " kb\n"); + + // total time used for this run echo "Time: " . Color::cyan(number_format(microtime(true) - $start, 10) . " s\n"); + // echo the average time per request - echo "Avg/lookup: " . Color::yellow(number_format((microtime(true) - $start) / $iterations, 10) . " s\n"); - echo "\n"; + echo "Avg/lookup: " . Color::yellow(number_format($reqs / $iterations, 10) . " s\n"); + + // echo the shortest lookup time + echo "Shortest lookup: " . Color::green(number_format($shortest, 10) . " s\n"); + + // echo the longest lookup time + echo "Longest lookup: " . Color::red(number_format($longest, 10) . " s\n"); + + echo Color::black("==============================================") . "\n"; + + // echo the longest and shortest routes + echo "Shortest route: " . Color::green($shortestRoute) . "\n"; + echo "Longest route: " . Color::red($longestRoute) . "\n"; } // take a route tree (array) and store it in a file to be read