diff --git a/.gitignore b/.gitignore index 856e230..fb63f1b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ tests/trees - /vendor/ +index.php diff --git a/src/Router.php b/src/Router.php index bb9400e..eb4f33a 100644 --- a/src/Router.php +++ b/src/Router.php @@ -15,12 +15,12 @@ class Router */ public function add(string $method, string $route, callable $handler): Router { - // Expand the route into segments and make dynamic segments into a common placeholder + // expand the route into segments and make dynamic segments into a common placeholder $segments = array_map(function($segment) { return str_starts_with($segment, ':') ? ':x' : $segment; }, explode('/', trim($route, '/'))); - // Push each segment into the routes array as a node, except if this is the root 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) { // skip an empty segment, which allows us to register handlers for the root node @@ -43,20 +43,33 @@ class Router */ public function lookup(string $method, string $uri): array { + // normalize uri to be tolerant of trailing slashes + $uri = '/' . trim($uri, '/'); + // node is a reference to our current location in the node tree $node = $this->routes; - // params will hold any dynamic segments we find - $params = []; + // init the response array + $res = ['code' => 0, 'handler' => null, 'params' => []]; // if the URI is just a slash, we can return the handler for the root node if ($uri === '/') { - return isset($node[$method]) - ? ['code' => 200, 'handler' => $node[$method], 'params' => null] - : ['code' => 405, 'handler' => null, 'params' => null]; + if (!$this->checkForHandlers($node)) { + $res['code'] = 404; + return $res; + } + + if (isset($node[$method])) { + $res['code'] = 200; + $res['handler'] = $node[$method]; + } else { + $res['code'] = 405; + } + + return $res; } - // We'll split up the URI into segments and traverse the node tree + // we'll split up the URI into segments and traverse the node tree foreach (explode('/', trim($uri, '/')) as $segment) { // if there is a node for this segment, move to it if (isset($node[$segment])) { @@ -66,19 +79,31 @@ class Router // if there is a dynamic segment, move to it and store the value if (isset($node[':x'])) { - $params[] = $segment; + $res['params'][] = $segment; $node = $node[':x']; continue; } // if we can't find a node for this segment, return 404 - return ['code' => 404, 'handler' => null, 'params' => []]; + $res['code'] = 404; + return $res; + } + + // if no handlers exist at this node, it's not a valid endpoint - return 404 + if (!$this->checkForHandlers($node)) { + $res['code'] = 404; + return $res; } // if we found a handler for the method, return it and any params. if not, return a 405 - return isset($node[$method]) - ? ['code' => 200, 'handler' => $node[$method], 'params' => $params ?? []] - : ['code' => 405, 'handler' => null, 'params' => []]; + if (isset($node[$method])) { + $res['code'] = 200; + $res['handler'] = $node[$method]; + } else { + $res['code'] = 405; + } + + return $res; } /** @@ -98,6 +123,21 @@ class Router return $this->routes; } + /** + * Check if a given node has any method handlers. + */ + private function checkForHandlers(array $node): bool + { + $hasHandlers = false; + foreach (['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'] as $m) { + if (isset($node[$m])) { + $hasHandlers = true; + break; + } + } + return $hasHandlers; + } + /** * Register a GET route. */ diff --git a/tests/test.php b/tests/test.php index 7bef9d9..aab0bd4 100644 --- a/tests/test.php +++ b/tests/test.php @@ -1,13 +1,12 @@