Skip to content

(feat) Routing: route parameter constraints - #349

Open
techmahedy wants to merge 2 commits into
doppar:4.xfrom
techmahedy:routing
Open

techmahedy wants to merge 2 commits into
doppar:4.xfrom
techmahedy:routing

Conversation

@techmahedy

@techmahedy techmahedy commented Oct 2, 2026 •

Copy link
Copy Markdown
Member

Pull Request Checklist

Q A
Branch? 4.x
Bug fix? partly (inline {id:regex} was parsed but ignored)
New feature? yes
Deprecations? no
Issues -
License MIT

Summary

A route parameter used to match any single URI segment, so posts/{id} also matched /posts/abc and the handler received a value in the wrong format. There was no way to constrain it. This PR adds parameter constraints to both attribute-based and file-based routes.

// Attribute routes
#[Route(uri: 'posts/{id}', where: ['id' => '[0-9]+'])]
#[Route(uri: 'archive/{year:[0-9]{4}}')]

// Route facade
Route::get('posts/{id}', [PostController::class, 'show'])->where('id', '[0-9]+');
Route::get('posts/{id}', [PostController::class, 'show'])->whereNumber('id');
Route::get('year/{y:[0-9]{4}}', ...);

// Once, for every {id}
Route::pattern('id', '[0-9]+');

A request that fails the constraint skips that route: the router tries the next one, then falls back to the existing 404. This also allows posts/{id} (numeric) and posts/{slug} to coexist.

Bug fix

The router already parsed {param:regex} (in convertRouteToRegex, extractRouteParameters, route() and getCurrentRouteMiddleware) but then discarded the regex and always matched [^\/]+. The inline form is now honoured.

New API

API Behaviour
->where(string|array $name, ?string $expression) Constrain one parameter, or several with a map
->whereNumber(), whereAlpha(), whereAlphaNumeric(), whereUuid(), whereUlid() Common formats; accept a name or an array of names
->whereIn($param, array $values) One of the listed values (quoted)
Route::pattern($name, $expression) Default constraint for every parameter of that name on routes registered afterwards; a constraint on the route wins
{id:regex} in a URI Inline form, works for facade and attribute routes
#[Route(..., where: [...])] New last argument of the attribute

Compatibility

  • Additive. Routes without constraints behave exactly as before.
  • #[Route] gained where as its last constructor argument, so positional callers are unaffected.
  • A route that previously relied on matching a non-conforming value only changes if you add a constraint to it.

Files

  • src/Phaseolies/Support/Router.php: where*(), pattern(), constraint-aware regex compilation, route re-keying, validation.
  • src/Phaseolies/Support/Router/Attributes/Route.php: where argument.
  • src/Phaseolies/Support/Facades/Route.php: docblock for the new methods.
  • tests/Router/RouteConstraintsTest.php: new.

Checklist

  • Tests have been added or updated
  • Documentation has been updated
  • Code follows the project coding standards
  • All tests pass locally

@techmahedy techmahedy self-assigned this Oct 2, 2026
@techmahedy techmahedy added the feat new feature label Oct 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feat new feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant