From 780525f090e3ac64a2a7555b2428ac4c8dd2fdd5 Mon Sep 17 00:00:00 2001 From: Vincent Hagen Date: Fri, 8 Nov 2024 22:44:55 +0100 Subject: [PATCH] refactor(routing): remove recursion in favor of iteration --- .../Routing/Construction/RouteTreeNode.php | 44 ++++++++----------- .../src/Routing/Construction/RoutingTree.php | 10 +++-- 2 files changed, 25 insertions(+), 29 deletions(-) diff --git a/src/Tempest/Http/src/Routing/Construction/RouteTreeNode.php b/src/Tempest/Http/src/Routing/Construction/RouteTreeNode.php index 6de4a9aa2..c2a0cf964 100644 --- a/src/Tempest/Http/src/Routing/Construction/RouteTreeNode.php +++ b/src/Tempest/Http/src/Routing/Construction/RouteTreeNode.php @@ -17,7 +17,7 @@ final class RouteTreeNode /** @var array */ private array $dynamicPaths = []; - private ?MarkedRoute $leaf = null; + private ?MarkedRoute $targetRoute = null; private function __construct( public readonly RouteTreeNodeType $type, @@ -40,34 +40,26 @@ public static function createStaticRouteNode(string $name): self return new self(RouteTreeNodeType::Static, $name); } - public function addPath(array $pathSegments, MarkedRoute $markedRoute): void + public function findOrCreateNodeForSegment(string $routeSegment): self { - // If path segments is empty this node should target to given marked route - if ($pathSegments === []) { - if ($this->leaf !== null) { - throw new DuplicateRouteException($markedRoute->route); - } - - $this->leaf = $markedRoute; + // Translates a path segment like {id} into it's matching regex. Static segments remain the same + $regexRouteSegment = self::convertDynamicSegmentToRegex($routeSegment); - return; + // Returns a static or dynamic child node depending on the segment is dynamic or static + if ($routeSegment === $regexRouteSegment) { + return $this->staticPaths[$regexRouteSegment] ??= self::createStaticRouteNode($routeSegment); } - // Removes the first element of the pathSegments and use it to determin the next routing node - $currentPathSegment = array_shift($pathSegments); - - // Translates a path segment like {id} into it's matching regex. Static segments remain the same - $regexPathSegment = self::convertDynamicSegmentToRegex($currentPathSegment); + return $this->dynamicPaths[$regexRouteSegment] ??= self::createDynamicRouteNode($regexRouteSegment); + } - // Find or create the next node to recurse into - if ($currentPathSegment !== $regexPathSegment) { - $node = $this->dynamicPaths[$regexPathSegment] ??= self::createDynamicRouteNode($regexPathSegment); - } else { - $node = $this->staticPaths[$regexPathSegment] ??= self::createStaticRouteNode($currentPathSegment); + public function setTargetRoute(MarkedRoute $markedRoute): void + { + if ($this->targetRoute !== null) { + throw new DuplicateRouteException($markedRoute->route); } - // Recurse into the newly created node to add the remainder of the path segments - $node->addPath($pathSegments, $markedRoute); + $this->targetRoute = $markedRoute; } private static function convertDynamicSegmentToRegex(string $uriPart): string @@ -107,14 +99,14 @@ public function toRegex(): string // Add a leaf alteration with an optional slash and end of line match `$`. // The `(*MARK:x)` is a marker which when this regex is matched will cause the matches array to contain // a key `"MARK"` with value `"x"`, it is used to track which route has been matched - if ($this->leaf !== null) { - $regexp .= '|\/?$(*' . MarkedRoute::REGEX_MARK_TOKEN . ':' . $this->leaf->mark . ')'; + if ($this->targetRoute !== null) { + $regexp .= '|\/?$(*' . MarkedRoute::REGEX_MARK_TOKEN . ':' . $this->targetRoute->mark . ')'; } $regexp .= ")"; - } elseif ($this->leaf !== null) { + } elseif ($this->targetRoute !== null) { // Add a singular leaf regex without alteration - $regexp .= '\/?$(*' . MarkedRoute::REGEX_MARK_TOKEN . ':' . $this->leaf->mark . ')'; + $regexp .= '\/?$(*' . MarkedRoute::REGEX_MARK_TOKEN . ':' . $this->targetRoute->mark . ')'; } return $regexp; diff --git a/src/Tempest/Http/src/Routing/Construction/RoutingTree.php b/src/Tempest/Http/src/Routing/Construction/RoutingTree.php index a371270d7..8c765ba95 100644 --- a/src/Tempest/Http/src/Routing/Construction/RoutingTree.php +++ b/src/Tempest/Http/src/Routing/Construction/RoutingTree.php @@ -22,10 +22,14 @@ public function add(MarkedRoute $markedRoute): void $method = $markedRoute->route->method; // Find the root tree node based on HTTP method - $root = $this->roots[$method->value] ??= RouteTreeNode::createRootRoute(); + $node = $this->roots[$method->value] ??= RouteTreeNode::createRootRoute(); - // Add path to tree using recursion - $root->addPath($markedRoute->route->split(), $markedRoute); + // Traverse the tree and find the node for each route segment + foreach ($markedRoute->route->split() as $routeSegment) { + $node = $node->findOrCreateNodeForSegment($routeSegment); + } + + $node->setTargetRoute($markedRoute); } /** @return array */