diff --git a/VERSION b/VERSION index 63815a2..c5f04ce 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.2503.230337 +0.2503.230355 diff --git a/src/Http/Routing/Filters/PatternFilter.php b/src/Http/Routing/Filters/PatternFilter.php index 74e024e..2df350d 100644 --- a/src/Http/Routing/Filters/PatternFilter.php +++ b/src/Http/Routing/Filters/PatternFilter.php @@ -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 { diff --git a/src/Http/Routing/Routes/PatternRoute.php b/src/Http/Routing/Routes/PatternRoute.php index 4df90cf..523f51f 100644 --- a/src/Http/Routing/Routes/PatternRoute.php +++ b/src/Http/Routing/Routes/PatternRoute.php @@ -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 { diff --git a/src/Http/Routing/UriMatchers/ExactPathUriMatcher.php b/src/Http/Routing/UriMatchers/ExactPathUriMatcher.php index f71f8d4..ef7289d 100644 --- a/src/Http/Routing/UriMatchers/ExactPathUriMatcher.php +++ b/src/Http/Routing/UriMatchers/ExactPathUriMatcher.php @@ -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 . '/'); } } diff --git a/tests/RouterTest.php b/tests/RouterTest.php index 28941f5..298fb2b 100644 --- a/tests/RouterTest.php +++ b/tests/RouterTest.php @@ -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 {