Skip to content

Commit 32aaff4

Browse files
authored
perf(routing): replace recursion in favor of iteration (#705)
1 parent 6300617 commit 32aaff4

File tree

2 files changed

+25
-29
lines changed

2 files changed

+25
-29
lines changed

src/Tempest/Http/src/Routing/Construction/RouteTreeNode.php

Lines changed: 18 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ final class RouteTreeNode
1717
/** @var array<string, RouteTreeNode> */
1818
private array $dynamicPaths = [];
1919

20-
private ?MarkedRoute $leaf = null;
20+
private ?MarkedRoute $targetRoute = null;
2121

2222
private function __construct(
2323
public readonly RouteTreeNodeType $type,
@@ -40,34 +40,26 @@ public static function createStaticRouteNode(string $name): self
4040
return new self(RouteTreeNodeType::Static, $name);
4141
}
4242

43-
public function addPath(array $pathSegments, MarkedRoute $markedRoute): void
43+
public function findOrCreateNodeForSegment(string $routeSegment): self
4444
{
45-
// If path segments is empty this node should target to given marked route
46-
if ($pathSegments === []) {
47-
if ($this->leaf !== null) {
48-
throw new DuplicateRouteException($markedRoute->route);
49-
}
50-
51-
$this->leaf = $markedRoute;
45+
// Translates a path segment like {id} into it's matching regex. Static segments remain the same
46+
$regexRouteSegment = self::convertDynamicSegmentToRegex($routeSegment);
5247

53-
return;
48+
// Returns a static or dynamic child node depending on the segment is dynamic or static
49+
if ($routeSegment === $regexRouteSegment) {
50+
return $this->staticPaths[$regexRouteSegment] ??= self::createStaticRouteNode($routeSegment);
5451
}
5552

56-
// Removes the first element of the pathSegments and use it to determin the next routing node
57-
$currentPathSegment = array_shift($pathSegments);
58-
59-
// Translates a path segment like {id} into it's matching regex. Static segments remain the same
60-
$regexPathSegment = self::convertDynamicSegmentToRegex($currentPathSegment);
53+
return $this->dynamicPaths[$regexRouteSegment] ??= self::createDynamicRouteNode($regexRouteSegment);
54+
}
6155

62-
// Find or create the next node to recurse into
63-
if ($currentPathSegment !== $regexPathSegment) {
64-
$node = $this->dynamicPaths[$regexPathSegment] ??= self::createDynamicRouteNode($regexPathSegment);
65-
} else {
66-
$node = $this->staticPaths[$regexPathSegment] ??= self::createStaticRouteNode($currentPathSegment);
56+
public function setTargetRoute(MarkedRoute $markedRoute): void
57+
{
58+
if ($this->targetRoute !== null) {
59+
throw new DuplicateRouteException($markedRoute->route);
6760
}
6861

69-
// Recurse into the newly created node to add the remainder of the path segments
70-
$node->addPath($pathSegments, $markedRoute);
62+
$this->targetRoute = $markedRoute;
7163
}
7264

7365
private static function convertDynamicSegmentToRegex(string $uriPart): string
@@ -107,14 +99,14 @@ public function toRegex(): string
10799
// Add a leaf alteration with an optional slash and end of line match `$`.
108100
// The `(*MARK:x)` is a marker which when this regex is matched will cause the matches array to contain
109101
// a key `"MARK"` with value `"x"`, it is used to track which route has been matched
110-
if ($this->leaf !== null) {
111-
$regexp .= '|\/?$(*' . MarkedRoute::REGEX_MARK_TOKEN . ':' . $this->leaf->mark . ')';
102+
if ($this->targetRoute !== null) {
103+
$regexp .= '|\/?$(*' . MarkedRoute::REGEX_MARK_TOKEN . ':' . $this->targetRoute->mark . ')';
112104
}
113105

114106
$regexp .= ")";
115-
} elseif ($this->leaf !== null) {
107+
} elseif ($this->targetRoute !== null) {
116108
// Add a singular leaf regex without alteration
117-
$regexp .= '\/?$(*' . MarkedRoute::REGEX_MARK_TOKEN . ':' . $this->leaf->mark . ')';
109+
$regexp .= '\/?$(*' . MarkedRoute::REGEX_MARK_TOKEN . ':' . $this->targetRoute->mark . ')';
118110
}
119111

120112
return $regexp;

src/Tempest/Http/src/Routing/Construction/RoutingTree.php

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,14 @@ public function add(MarkedRoute $markedRoute): void
2222
$method = $markedRoute->route->method;
2323

2424
// Find the root tree node based on HTTP method
25-
$root = $this->roots[$method->value] ??= RouteTreeNode::createRootRoute();
25+
$node = $this->roots[$method->value] ??= RouteTreeNode::createRootRoute();
2626

27-
// Add path to tree using recursion
28-
$root->addPath($markedRoute->route->split(), $markedRoute);
27+
// Traverse the tree and find the node for each route segment
28+
foreach ($markedRoute->route->split() as $routeSegment) {
29+
$node = $node->findOrCreateNodeForSegment($routeSegment);
30+
}
31+
32+
$node->setTargetRoute($markedRoute);
2933
}
3034

3135
/** @return array<string, string> */

0 commit comments

Comments
 (0)