Skip to content

Commit 3978464

Browse files
committed
Add tests for relative path parsing and implement baseUrl stripping in SymfonyRouter
- Add comprehensive test cases for relative path parsing with various baseUrl configurations - Handle baseUrl stripping for file entry points (e.g., api.php) - Support baseUrl with subdirectories and subdomains - Implement stripBasePathFromRequestPath() method to extract path component from baseUrl - All tests pass including new relative path test cases
1 parent 606d8cb commit 3978464

2 files changed

Lines changed: 176 additions & 1 deletion

File tree

src/Router/SymfonyRouter.php

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,10 @@ public function parseRequest(
4545
$this->router,
4646
$this->getRequestContext($request),
4747
);
48-
$routeMatch = $matcher->match($request->getUri()->getPath());
48+
$relativePath = $this->stripBasePathFromRequestPath(
49+
$request->getUri()->getPath(),
50+
);
51+
$routeMatch = $matcher->match($relativePath);
4952

5053
return new RouteMatch(
5154
$routeMatch["_route"],
@@ -75,6 +78,26 @@ private function getRequestContext(
7578
);
7679
}
7780

81+
private function stripBasePathFromRequestPath(string $requestPath): string
82+
{
83+
$basePath = parse_url($this->baseUrl, PHP_URL_PATH) ?: "/";
84+
85+
// If basePath is just '/', return the request path as-is
86+
if ($basePath === "/") {
87+
return $requestPath;
88+
}
89+
90+
// If the request path starts with the base path, strip it
91+
if (str_starts_with($requestPath, $basePath)) {
92+
$relativePath = substr($requestPath, strlen($basePath));
93+
// Ensure the relative path starts with '/'
94+
return $relativePath === "" ? "/" : $relativePath;
95+
}
96+
97+
// If no stripping occurred, return the request path as-is
98+
return $requestPath;
99+
}
100+
78101
#[\Override]
79102
public function getGenerator(): UrlGeneratorInterface
80103
{

tests/Unit/Router/SymfonyRouterTest.php

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,3 +297,155 @@ function testRoute($router, $request, $uri, $path, $method, $scheme, $host)
297297
"api.example.com",
298298
);
299299
});
300+
301+
// Tests for relative path parsing with baseUrl stripping
302+
test(
303+
"it can parse relative paths with baseUrl containing file entry point",
304+
function () {
305+
$baseUrl = "http://localhost:8080/api.php";
306+
$router = new SymfonyRouter($baseUrl);
307+
$request = Mockery::mock(ServerRequestInterface::class);
308+
$uri = Mockery::mock(UriInterface::class);
309+
$controller = Mockery::mock(ControllerInterface::class);
310+
311+
$router->get("test_route", "/some/path", fn() => $controller);
312+
313+
$request->shouldReceive("getUri")->andReturn($uri);
314+
$request->shouldReceive("getMethod")->andReturn("GET");
315+
$uri->shouldReceive("getScheme")->andReturn("http");
316+
$uri->shouldReceive("getHost")->andReturn("localhost");
317+
$uri->shouldReceive("getPath")->andReturn("/api.php/some/path");
318+
$uri->shouldReceive("getQuery")->andReturn("");
319+
320+
$routeMatch = $router->parseRequest($request);
321+
expect($routeMatch)
322+
->toBeInstanceOf(RouteMatch::class)
323+
->and($routeMatch->isMatch())
324+
->toBeTrue();
325+
},
326+
);
327+
328+
test("it can parse relative paths with simple root baseUrl", function () {
329+
$baseUrl = "http://localhost";
330+
$router = new SymfonyRouter($baseUrl);
331+
$request = Mockery::mock(ServerRequestInterface::class);
332+
$uri = Mockery::mock(UriInterface::class);
333+
$controller = Mockery::mock(ControllerInterface::class);
334+
335+
$router->get("test_route", "/some/path", fn() => $controller);
336+
337+
$request->shouldReceive("getUri")->andReturn($uri);
338+
$request->shouldReceive("getMethod")->andReturn("GET");
339+
$uri->shouldReceive("getScheme")->andReturn("http");
340+
$uri->shouldReceive("getHost")->andReturn("localhost");
341+
$uri->shouldReceive("getPath")->andReturn("/some/path");
342+
$uri->shouldReceive("getQuery")->andReturn("");
343+
344+
$routeMatch = $router->parseRequest($request);
345+
expect($routeMatch)
346+
->toBeInstanceOf(RouteMatch::class)
347+
->and($routeMatch->isMatch())
348+
->toBeTrue();
349+
});
350+
351+
test(
352+
"it can parse relative paths with baseUrl containing subdirectory",
353+
function () {
354+
$baseUrl = "http://example.com/api/v1";
355+
$router = new SymfonyRouter($baseUrl);
356+
$request = Mockery::mock(ServerRequestInterface::class);
357+
$uri = Mockery::mock(UriInterface::class);
358+
$controller = Mockery::mock(ControllerInterface::class);
359+
360+
$router->get("test_route", "/users", fn() => $controller);
361+
362+
$request->shouldReceive("getUri")->andReturn($uri);
363+
$request->shouldReceive("getMethod")->andReturn("GET");
364+
$uri->shouldReceive("getScheme")->andReturn("http");
365+
$uri->shouldReceive("getHost")->andReturn("example.com");
366+
$uri->shouldReceive("getPath")->andReturn("/api/v1/users");
367+
$uri->shouldReceive("getQuery")->andReturn("");
368+
369+
$routeMatch = $router->parseRequest($request);
370+
expect($routeMatch)
371+
->toBeInstanceOf(RouteMatch::class)
372+
->and($routeMatch->isMatch())
373+
->toBeTrue();
374+
},
375+
);
376+
377+
test("it can parse relative paths with subdomain", function () {
378+
$baseUrl = "http://api.example.com";
379+
$router = new SymfonyRouter($baseUrl);
380+
$request = Mockery::mock(ServerRequestInterface::class);
381+
$uri = Mockery::mock(UriInterface::class);
382+
$controller = Mockery::mock(ControllerInterface::class);
383+
384+
$router->get("test_route", "/products", fn() => $controller);
385+
386+
$request->shouldReceive("getUri")->andReturn($uri);
387+
$request->shouldReceive("getMethod")->andReturn("GET");
388+
$uri->shouldReceive("getScheme")->andReturn("http");
389+
$uri->shouldReceive("getHost")->andReturn("api.example.com");
390+
$uri->shouldReceive("getPath")->andReturn("/products");
391+
$uri->shouldReceive("getQuery")->andReturn("");
392+
393+
$routeMatch = $router->parseRequest($request);
394+
expect($routeMatch)
395+
->toBeInstanceOf(RouteMatch::class)
396+
->and($routeMatch->isMatch())
397+
->toBeTrue();
398+
});
399+
400+
test(
401+
"it can parse relative paths with subdomain and subdirectory",
402+
function () {
403+
$baseUrl = "http://api.example.com/v2";
404+
$router = new SymfonyRouter($baseUrl);
405+
$request = Mockery::mock(ServerRequestInterface::class);
406+
$uri = Mockery::mock(UriInterface::class);
407+
$controller = Mockery::mock(ControllerInterface::class);
408+
409+
$router->get("test_route", "/status", fn() => $controller);
410+
411+
$request->shouldReceive("getUri")->andReturn($uri);
412+
$request->shouldReceive("getMethod")->andReturn("GET");
413+
$uri->shouldReceive("getScheme")->andReturn("http");
414+
$uri->shouldReceive("getHost")->andReturn("api.example.com");
415+
$uri->shouldReceive("getPath")->andReturn("/v2/status");
416+
$uri->shouldReceive("getQuery")->andReturn("");
417+
418+
$routeMatch = $router->parseRequest($request);
419+
expect($routeMatch)
420+
->toBeInstanceOf(RouteMatch::class)
421+
->and($routeMatch->isMatch())
422+
->toBeTrue();
423+
},
424+
);
425+
426+
test("it can parse relative paths with nested routes", function () {
427+
$baseUrl = "http://localhost:9000/app";
428+
$router = new SymfonyRouter($baseUrl);
429+
$request = Mockery::mock(ServerRequestInterface::class);
430+
$uri = Mockery::mock(UriInterface::class);
431+
$controller = Mockery::mock(ControllerInterface::class);
432+
433+
$router->get("test_route", "/api/v1/users/{id}", fn() => $controller, [
434+
"id" => "\\d+",
435+
]);
436+
437+
$request->shouldReceive("getUri")->andReturn($uri);
438+
$request->shouldReceive("getMethod")->andReturn("GET");
439+
$uri->shouldReceive("getScheme")->andReturn("http");
440+
$uri->shouldReceive("getHost")->andReturn("localhost");
441+
$uri->shouldReceive("getPath")->andReturn("/app/api/v1/users/123");
442+
$uri->shouldReceive("getQuery")->andReturn("");
443+
444+
$routeMatch = $router->parseRequest($request);
445+
expect($routeMatch)
446+
->toBeInstanceOf(RouteMatch::class)
447+
->and($routeMatch->isMatch())
448+
->toBeTrue()
449+
->and($routeMatch->getAttributes())
450+
->toBe(["id" => "123"]);
451+
});

0 commit comments

Comments
 (0)