Account for trailing slashes in URLs.

This commit is contained in:
flash 2025-03-23 03:45:47 +00:00
parent 2bb9cccf18
commit 2372a113d2
Signed by: flash
GPG key ID: 2C9C2C574D47FE3E
5 changed files with 20 additions and 9 deletions

View file

@ -1 +1 @@
0.2503.230337
0.2503.230355

View file

@ -1,7 +1,7 @@
<?php
// PatternFilter.php
// Created: 2024-03-28
// Updated: 2025-03-07
// Updated: 2025-03-23
namespace Index\Http\Routing\Filters;
@ -18,14 +18,14 @@ class PatternFilter extends FilterAttribute {
/**
* @param string $pattern URI path pattern.
* @param bool $raw If true, $pattern is taken as is, if false, $pattern is prefixed with #^ and suffixed with #u or $#uD
* @param bool $prefix Only has effect if $raw is false. If true is #u appended to $pattern, otherwise $#uD.
* @param bool $prefix Only has effect if $raw is false. If true is #u appended to $pattern, otherwise /?$#uD.
*/
public function __construct(
string $pattern,
bool $raw = false,
bool $prefix = true,
) {
$this->pattern = $raw ? $pattern : sprintf('#^%s%s', $pattern, $prefix ? '#u' : '$#uD');
$this->pattern = $raw ? $pattern : sprintf('#^%s%s', $pattern, $prefix ? '#u' : '/?$#uD');
}
public function createInstance(Closure $handler): FilterInfo {

View file

@ -1,7 +1,7 @@
<?php
// PatternRoute.php
// Created: 2024-03-28
// Updated: 2025-03-07
// Updated: 2025-03-23
namespace Index\Http\Routing\Routes;
@ -19,13 +19,15 @@ class PatternRoute extends RouteAttribute {
* @param string $method HTTP Method.
* @param string $pattern URI path pattern.
* @param bool $raw If true, $pattern is taken as is, if false, $pattern is prefixed with #^ and suffixed with $#uD
* @param bool $slash Only has effect if $raw is false. If true /? is appended to $pattern to allow for trailing slashes.
*/
public function __construct(
private string $method,
string $pattern,
bool $raw = false
bool $raw = false,
bool $slash = true,
) {
$this->pattern = $raw ? $pattern : sprintf('#^%s$#uD', $pattern);
$this->pattern = $raw ? $pattern : sprintf('#^%s%s$#uD', $pattern, $slash ? '/?' : '');
}
public function createInstance(Closure $handler): RouteInfo {

View file

@ -1,7 +1,7 @@
<?php
// ExactPathUriMatcher.php
// Created: 2025-03-07
// Updated: 2025-03-07
// Updated: 2025-03-23
namespace Index\Http\Routing\UriMatchers;
@ -19,6 +19,7 @@ class ExactPathUriMatcher implements UriMatcher {
) {}
public function match(UriInterface $uri): array|bool {
return $uri->getPath() === $this->path;
$path = $uri->getPath();
return $path === $this->path || $path === ($this->path . '/');
}
}

View file

@ -139,6 +139,12 @@ final class RouterTest extends TestCase {
return 'hit pattern';
}
#[ExactRoute('GET', '/assets/avatar')]
#[PatternRoute('GET', '/assets/avatar/([0-9]+)(?:\.[a-z]+)?')]
public function getTrailingSlash(): string {
return 'hit';
}
public function hasNoAttr(): string {
return 'not a route';
}
@ -163,6 +169,8 @@ final class RouterTest extends TestCase {
$this->assertSame('1RJNSRYmxrvXUr////jpg', (string)$router->handle(HttpRequest::createRequestWithoutBody('GET', '/uploads/1RJNSRYmxrvXUr.jpg'))->getBody());
$this->assertSame('hit exact', (string)$router->handle(HttpRequest::createRequestWithoutBody('GET', '/messages/stats'))->getBody());
$this->assertSame('hit pattern', (string)$router->handle(HttpRequest::createRequestWithoutBody('GET', '/messages/soaps'))->getBody());
$this->assertSame('hit', (string)$router->handle(HttpRequest::createRequestWithoutBody('GET', '/assets/avatar/'))->getBody());
$this->assertSame('hit', (string)$router->handle(HttpRequest::createRequestWithoutBody('GET', '/assets/avatar/123/'))->getBody());
}
public function testEEPROMSituation(): void {