diff --git a/SegmentRouter.php b/SegmentRouter.php new file mode 100644 index 0000000..05fc18b --- /dev/null +++ b/SegmentRouter.php @@ -0,0 +1,59 @@ +routes; + foreach ($segments as $segment) $node = &$node[$segment]; + + // Add the handler to the last node + $node[$method] = $handler; + } + + public function lookup(string $method, string $uri): int + { + // Expand the URI into segments + $uriSegments = explode('/', trim($uri, '/')); + $node = $this->routes; + $params = []; + + // Traverse the routes array to find the handler + foreach ($uriSegments as $segment) { + // Check if the segment exists in the node, or if there's a dynamic segment + if (!isset($node[$segment])) { + $dynamicSegment = $this->matchDynamicSegment($node, $segment); + if ($dynamicSegment) { + $params[] = $segment; + $node = $node[$dynamicSegment]; + } else { + return 404; + } + } else { + $node = $node[$segment]; + } + } + + // If the HTTP method is not supported, return 405 + if (!isset($node[$method])) return 405; + + // Call the handler + $handler = $node[$method]; + call_user_func_array($handler, $params); + return 200; + } + + private function matchDynamicSegment(array $node, string $segment) + { + foreach ($node as $key => $value) { + if (strpos($key, ':') === 0) return $key; + } + return null; + } +} diff --git a/TrieRouter.php b/TrieRouter.php new file mode 100644 index 0000000..b9d4ede --- /dev/null +++ b/TrieRouter.php @@ -0,0 +1,71 @@ +{strtoupper($method)}; + $segments = explode('/', trim($route, '/')); + + foreach ($segments as $segment) { + if (!isset($node[$segment])) { + $node[$segment] = ['_children' => [], '_handler' => null]; + } + $node = &$node[$segment]['_children']; + } + + $node['_handler'] = $handler; + } + + // Find and handle the route + public function handleRequest(string $method, string $uri) + { + $node = &$this->{strtoupper($method)}; + $segments = explode('/', trim($uri, '/')); + $params = []; + + foreach ($segments as $segment) { + if (isset($node[$segment])) { + $node = &$node[$segment]['_children']; + } else { + // Handle dynamic parameters (e.g., :id) + $dynamicSegment = $this->matchDynamicSegment($node, $segment); + if ($dynamicSegment) { + $params[] = $segment; + $node = &$node[$dynamicSegment]['_children']; + } else { + return $this->notFound(); + } + } + } + + // Check if a handler exists for the current node + if (isset($node['_handler'])) { + return call_user_func_array($node['_handler'], $params); + } + + return $this->notFound(); + } + + // Match dynamic route segments like ':id' + private function matchDynamicSegment(array $node, string $segment) + { + foreach ($node as $key => $value) { + if (strpos($key, ':') === 0) return $key; + } + return null; + } + + // Default 404 handler + private function notFound() + { + echo "404 Not Found"; + return false; + } +} diff --git a/tests/array.php b/tests/array.php new file mode 100644 index 0000000..8fde1b1 --- /dev/null +++ b/tests/array.php @@ -0,0 +1,61 @@ +add($method, $path, $handler); +} + +for ($i = 0; $i < 10; $i++) { + [$method, $uri] = $routes[array_rand($routes)]; + + // Generate some random parameters + $uri = str_replace(':id', rand(1, 100), $uri); + $uri = str_replace(':slug', 'slug-' . rand(1, 100), $uri); + $uri = str_replace(':page', rand(1, 100), $uri); + $uri = str_replace(':extra', 'extra-' . rand(1, 100), $uri); + + $res = $r->lookup($method, $uri); + if ($res !== 200) { + echo "Failed to handle request for $uri - $res\n"; + exit(1); + } +} + +exit(0); diff --git a/tests/tools.php b/tests/tools.php new file mode 100644 index 0000000..c9e87a2 --- /dev/null +++ b/tests/tools.php @@ -0,0 +1,117 @@ +add($method, $path, function() { + return true; + }); + } + return $array; +} + +function runIterations(int $iterations, $r, array $routes) { + echo "Running $iterations iterations\n"; + $start = microtime(true); + $interval = $iterations / 10; + for ($i = 0; $i < $iterations; $i++) { + // pick a random route from the array + [$method, $uri] = $routes[array_rand($routes)]; + $res = $r->lookup($method, $uri); + if ($res !== 200) { + echo Color::red("Failed to handle request for $uri - $res\n"); + exit(1); + } + if ($i !== 0 && $i % ($interval) === 0) echoMemoryAndTime($i, $start); + } + 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"; +} diff --git a/tests/trie.php b/tests/trie.php new file mode 100644 index 0000000..49b6a35 --- /dev/null +++ b/tests/trie.php @@ -0,0 +1,60 @@ +handleRequest($method, $uri); + if ($res !== true) { + echo "Failed to handle request for $uri\n"; + exit(1); + } + if ($i !== 0 && $i % ($interval) === 0) echoMemoryAndTime($i, $start); + } + 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"; +} + +echoTitle("Starting blog lookups"); +runIterations(100000, $b, $blog); +runIterations(1000000, $b, $blog); + +echoTitle("Starting github lookups"); +runIterations(10000, $g, $github); +runIterations(100000, $g, $github); +runIterations(1000000, $g, $github); + +echoTitle("Starting big lookups"); +runIterations(10000, $big, $bigRoutes); +runIterations(100000, $big, $bigRoutes); +runIterations(1000000, $big, $bigRoutes); + +exit(0);