Skip to content

Commit cbe8fc2

Browse files
committed
Add Unittests for new changes
1 parent ac02ed6 commit cbe8fc2

File tree

5 files changed

+230
-6
lines changed

5 files changed

+230
-6
lines changed

src/Map.php

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -431,20 +431,32 @@ public function getAsTreeRouteNode()
431431
{
432432
$treeRoutes = [];
433433
foreach ($this->routes as $route) {
434-
if (! $route->isRoutable) {
434+
if (! $route->isRoutable || $route->path === null) {
435435
continue;
436436
}
437437

438-
// replace all parameters with {}
438+
// replace "{/year,month,day}" parameters with /{}/{}/{}
439+
$routePath = preg_replace_callback(
440+
'~{/((?:\w+,?)+)}~',
441+
static function (array $matches) {
442+
$variables = explode(',', $matches[1]);
443+
444+
return '/' . implode('/', array_fill(0, count($variables), '{}'));
445+
},
446+
$route->path
447+
) ?: $route->path;
448+
$paramsAreOptional = $routePath !== $route->path;
449+
439450
// This regexp will also work with "{controller:[a-zA-Z][a-zA-Z0-9_-]{1,}}"
440-
$routePath = preg_replace('~{(?:[^{}]*|(?R))*}~', '{}', $route->path);
451+
$routePath = preg_replace('~{(?:[^{}]*|(?R))*}~', '{}', $routePath) ?: $routePath;
441452
$node = &$treeRoutes;
442453
foreach (explode('/', trim($routePath, '/')) as $segment) {
443-
if (strpos($segment, '{') === 0 || strpos($segment, ':') === 0) {
444-
for ($i = 0; $i <= substr_count($segment, ','); $i++) {
445-
$node = &$node['{}'];
454+
if (strpos($segment, '{') === 0) {
455+
if ($paramsAreOptional) {
446456
$node[spl_object_hash($route)] = $route;
447457
}
458+
$node = &$node['{}'];
459+
$node[spl_object_hash($route)] = $route;
448460
continue;
449461
}
450462
$node = &$node[$segment];

tests/Benchmark/GeneratorBench.php

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php
2+
3+
namespace Aura\Router\Benchmark;
4+
5+
use Aura\Router\Generator;
6+
use Aura\Router\Map;
7+
use Aura\Router\Route;
8+
9+
/**
10+
* @BeforeMethods("setUp")
11+
*/
12+
class GeneratorBench
13+
{
14+
/**
15+
* @var Generator
16+
*/
17+
private $generator;
18+
19+
public function setUp()
20+
{
21+
$map = new Map(new Route());
22+
foreach ($this->routesProvider() as $key => $route) {
23+
$map->get($key, $route, static function () use ($key) { return $key; });
24+
}
25+
26+
$map->get('dummy', '/api/user/{id}/{action}/{controller:[a-zA-Z][a-zA-Z0-9_-]{1,}}{/param1,param2}');
27+
$this->generator = new Generator($map);
28+
}
29+
30+
31+
private function routesProvider()
32+
{
33+
$segments = ['one', 'two', 'three', 'four', 'five', 'six'];
34+
$routesPerSegment = 100;
35+
36+
$routeSegment = '';
37+
foreach ($segments as $index => $segment) {
38+
$routeSegment .= '/' . $segment;
39+
for ($i = 1; $i <= $routesPerSegment; $i++) {
40+
yield $index . '-' . $i => $routeSegment . $i;
41+
}
42+
}
43+
}
44+
45+
46+
/**
47+
* @Revs(1000)
48+
* @Iterations (10)
49+
*/
50+
public function benchMatch()
51+
{
52+
$this->generator->generate('dummy', [
53+
'id' => 1,
54+
'action' => 'doSomethingAction',
55+
'controller' => 'My_User-Controller1',
56+
'param1' => 'value1',
57+
'param2' => 'value2',
58+
]);
59+
}
60+
}

tests/Benchmark/MatchBench.php

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<?php
2+
3+
namespace Aura\Router\Benchmark;
4+
5+
use Aura\Router\RouterContainer;
6+
use GuzzleHttp\Psr7\ServerRequest;
7+
use Psr\Http\Message\ServerRequestInterface;
8+
9+
/**
10+
* @BeforeMethods("setUp")
11+
*/
12+
class MatchBench
13+
{
14+
/** @var RouterContainer $container */
15+
private $container;
16+
/**
17+
* @var \Aura\Router\Route[]|\mixed[][]
18+
*/
19+
private $treeNodes;
20+
21+
public function setUp()
22+
{
23+
$this->container = new RouterContainer();
24+
$map = $this->container->getMap();
25+
26+
foreach ($this->routesProvider() as $key => $route) {
27+
$map->get($key, $route, static function () use ($key) { return $key; });
28+
}
29+
30+
$this->treeNodes = $map->getAsTreeRouteNode();
31+
}
32+
33+
/**
34+
* @Iterations(3)
35+
*/
36+
public function benchMatch()
37+
{
38+
$this->container->getMap()->treeRoutes = $this->treeNodes;
39+
$matcher = $this->container->getMatcher();
40+
foreach ($this->routesProvider() as $route) {
41+
$result = $matcher->match($this->stringToRequest($route));
42+
if ($result === false) {
43+
throw new \RuntimeException(sprintf('Expected route "%s" to be an match', $route));
44+
}
45+
}
46+
}
47+
48+
private function routesProvider()
49+
{
50+
$segments = ['one', 'two', 'three', 'four', 'five', 'six'];
51+
$routesPerSegment = 100;
52+
53+
$routeSegment = '';
54+
foreach ($segments as $index => $segment) {
55+
$routeSegment .= '/' . $segment;
56+
for ($i = 1; $i <= $routesPerSegment; $i++) {
57+
yield $index . '-' . $i => $routeSegment . $i;
58+
}
59+
}
60+
}
61+
62+
/**
63+
* @param string $url
64+
* @return ServerRequestInterface
65+
*/
66+
private function stringToRequest($url)
67+
{
68+
return new ServerRequest('GET', $url, [], null, '1.1', []);
69+
}
70+
}

tests/MapTest.php

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,4 +137,53 @@ public function testGetAndSetRoutes()
137137
$this->assertIsRoute($actual['page.read']);
138138
$this->assertEquals('/page/{id}{format}', $actual['page.read']->path);
139139
}
140+
141+
public function testGetAsTreeRouteNodeSuccess()
142+
{
143+
$routes = [
144+
(new Route())->path('/api/users'),
145+
(new Route())->path('/api/users/{id}'),
146+
(new Route())->path('/api/users/{id}/delete'),
147+
(new Route())->path('/api/archive{/year,month,day}'),
148+
(new Route())->path('/api/{controller:[a-zA-Z][a-zA-Z0-9_-]{1,}}/{action}'),
149+
(new Route())->path('/api/users/{id}/not-routeable')->isRoutable(false),
150+
];
151+
$sut = new Map(new Route());
152+
$sut->setRoutes($routes);
153+
154+
$result = $sut->getAsTreeRouteNode();
155+
156+
$this->assertSame([
157+
'api' => [
158+
'users' => [
159+
spl_object_hash($routes[0]) => $routes[0],
160+
'{}' => [
161+
spl_object_hash($routes[1]) => $routes[1],
162+
spl_object_hash($routes[2]) => $routes[2],
163+
'delete' => [
164+
spl_object_hash($routes[2]) => $routes[2],
165+
],
166+
],
167+
],
168+
'archive' => [
169+
spl_object_hash($routes[3]) => $routes[3],
170+
'{}' => [ // year
171+
spl_object_hash($routes[3]) => $routes[3],
172+
'{}' => [ // month
173+
spl_object_hash($routes[3]) => $routes[3],
174+
'{}' => [ // day
175+
spl_object_hash($routes[3]) => $routes[3],
176+
],
177+
],
178+
],
179+
],
180+
'{}' => [
181+
spl_object_hash($routes[4]) => $routes[4],
182+
'{}' => [
183+
spl_object_hash($routes[4]) => $routes[4],
184+
],
185+
],
186+
],
187+
], $result);
188+
}
140189
}

tests/MatcherTest.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
<?php
22
namespace Aura\Router;
33

4+
use Aura\Router\Rule\RuleIterator;
5+
use Psr\Log\LoggerInterface;
46
use Yoast\PHPUnitPolyfills\TestCases\TestCase;
57
use GuzzleHttp\Psr7\ServerRequest;
68

@@ -264,4 +266,35 @@ public function testLogger()
264266
$this->assertSame($expect, $actual);
265267
$this->assertRoute($bar, $matcher->getMatchedRoute());
266268
}
269+
270+
public function testMatchWithMatchedTree()
271+
{
272+
$routes = [
273+
(new Route())->path('/api/users'),
274+
(new Route())->path('/api/users/{id}'),
275+
(new Route())->path('/api/users/{id}/delete'),
276+
(new Route())->path('/api/archive{/year,month,day}'),
277+
(new Route())->path('/api/{controller:[a-zA-Z][a-zA-Z0-9_-]{1,}}/{action}'),
278+
(new Route())->path('/not-routeable')->isRoutable(false),
279+
];
280+
$map = new Map(new Route());
281+
$map->setRoutes($routes);
282+
283+
$sut = new Matcher(
284+
$map,
285+
$this->createMock(LoggerInterface::class),
286+
new RuleIterator()
287+
);
288+
289+
self::assertNotFalse($sut->match($this->newRequest('/api/users')));
290+
self::assertNotFalse($sut->match($this->newRequest('/api/users/1')));
291+
self::assertNotFalse($sut->match($this->newRequest('/api/users/1/delete')));
292+
self::assertNotFalse($sut->match($this->newRequest('/api/archive')));
293+
self::assertNotFalse($sut->match($this->newRequest('/api/archive/2025')));
294+
self::assertNotFalse($sut->match($this->newRequest('/api/archive/2025/05')));
295+
self::assertNotFalse($sut->match($this->newRequest('/api/archive/2025/05/22')));
296+
self::assertNotFalse($sut->match($this->newRequest('/api/valid-controller-name/action')));
297+
298+
self::assertFalse($sut->match($this->newRequest('/not-routeable')));
299+
}
267300
}

0 commit comments

Comments
 (0)