Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions src/Map.php
Original file line number Diff line number Diff line change
Expand Up @@ -420,4 +420,40 @@ public function attach($namePrefix, $pathPrefix, callable $callable)
$callable($this);
$this->protoRoute = $old;
}

/**
* Convert all routes into a node tree array structure that contains all possible routes per route segment
* This will reduce the amount of possible routes to check
*
* @return array<string, Route|array<string, mixed>>
*/
public function getAsTreeRouteNode()
{
$treeRoutes = [];
foreach ($this->routes as $route) {
if (! $route->isRoutable) {
continue;
}

// replace all parameters with {}
// This regexp will also work with "{controller:[a-zA-Z][a-zA-Z0-9_-]{1,}}"
$routePath = preg_replace('~{(?:[^{}]*|(?R))*}~', '{}', $route->path);
$node = &$treeRoutes;
foreach (explode('/', trim($routePath, '/')) as $segment) {
if (strpos($segment, '{') === 0 || strpos($segment, ':') === 0) {
for ($i = 0; $i <= substr_count($segment, ','); $i++) {
$node = &$node['{}'];
$node[spl_object_hash($route)] = $route;
}
continue;
}
$node = &$node[$segment];
}

$node[spl_object_hash($route)] = $route;
unset($node);
}

return $treeRoutes;
}
}
39 changes: 32 additions & 7 deletions src/Matcher.php
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,12 @@ public function match(ServerRequestInterface $request)
$this->failedScore = 0;
$path = $request->getUri()->getPath();

foreach ($this->map as $name => $proto) {
$route = $this->requestRoute($request, $proto, $name, $path);
$possibleRoutes = $this->getMatchedTree($path);
foreach ($possibleRoutes as $proto) {
if (is_array($proto)) {
continue;
}
$route = $this->requestRoute($request, $proto, $path);
if ($route) {
return $route;
}
Expand All @@ -131,20 +135,18 @@ public function match(ServerRequestInterface $request)
*
* @param Route $proto The proto-route to match against.
*
* @param string $name The route name.
*
* @param string $path The request path.
*
* @return mixed False on failure, or a Route on match.
*
*/
protected function requestRoute($request, $proto, $name, $path)
protected function requestRoute($request, $proto, $path)
{
if (! $proto->isRoutable) {
return;
return false;
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See doctype description: @return mixed False on failure, or a Route on match.
so this should return false?

}
$route = clone $proto;
return $this->applyRules($request, $route, $name, $path);
return $this->applyRules($request, $route, $route->name, $path);
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

routename is already available in the route itself

}

/**
Expand Down Expand Up @@ -261,4 +263,27 @@ public function getMatchedRoute()
{
return $this->matchedRoute;
}

/**
* Split the URL into URL Segments and check for matching routes per segment
* This segment could return a list of possible routes
*
* @param string $path
* @return \RecursiveArrayIterator
*/
private function getMatchedTree($path)
{
$node = $this->map->getAsTreeRouteNode();
foreach (explode('/', trim($path, '/')) as $segment) {
if (isset($node[$segment])) {
$node = $node[$segment];
continue;
}
if (isset($node['{}'])) {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Group all kind of routes with dynamic properties into a generic group

$node = $node['{}'];
}
}

return new \RecursiveArrayIterator($node);
}
Comment on lines +268 to +288
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

getMatchedTree() recomputes the tree on every request – negate intended optimisation

Calling $this->map->getAsTreeRouteNode() each time discards the cache benefit.
Persist the result in a private property (e.g., $this->tree) or have Map memoise internally.

-        $node = $this->map->getAsTreeRouteNode();
+        $node = $this->map->getTreeRoutesCached(); // new memoised accessor

With large route maps this will materially affect performance.

Committable suggestion skipped: line range outside the PR's diff.

}
1 change: 0 additions & 1 deletion tests/MatcherTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,6 @@ public function testLogger()
$matcher->match($request);

$expect = [
'debug: /bar FAILED Aura\Router\Rule\Path ON foo',
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will not work anymore because the /foo route will not be checked anymore.
Is this a problem for you? Do it really need hundreds of debug messages that a specific route didn't matched?

'debug: /bar MATCHED ON bar',
];
$actual = $logger->lines;
Expand Down
Loading