Skip to content

Commit 47a3924

Browse files
Feature additions, route constraints, and unit tests
1 parent 0cf43b6 commit 47a3924

File tree

3 files changed

+393
-22
lines changed

3 files changed

+393
-22
lines changed

README.md

+188-2
Original file line numberDiff line numberDiff line change
@@ -222,8 +222,6 @@ Example Usage:
222222
$route = Route::delete('delete_item', '/item/{id}', [ItemController::class, 'delete']);
223223
```
224224

225-
With these static methods, defining routes becomes a breeze, providing a smoother and more efficient way to handle routing in your PHP application.
226-
227225
### Using `where` Constraints in the Route Object
228226

229227
The `Route` object allows you to define constraints on route parameters using the `where` methods. These constraints validate and filter parameter values based on regular expressions. Here's how to use them:
@@ -324,6 +322,194 @@ Example Usage:
324322
$route = (new Route('category', '/category/{name}'))->whereAlpha('name');
325323
```
326324

325+
#### Method `whereTwoSegments()`
326+
327+
This method applies a constraint to match exactly two path segments separated by a slash.
328+
329+
```php
330+
/**
331+
* Sets a constraint for exactly two path segments separated by a slash.
332+
*
333+
* Example: /{segment1}/{segment2}
334+
*
335+
* @param mixed ...$parameters The route parameters to apply the constraint to.
336+
* @return self The updated Route instance.
337+
*/
338+
public function whereTwoSegments(...$parameters): self
339+
{
340+
$this->assignExprToParameters($parameters, '[a-zA-Z0-9\-_]+/[a-zA-Z0-9\-_]+');
341+
foreach ($parameters as $parameter) {
342+
$this->path = str_replace(sprintf('{%s}', $parameter), sprintf('{%s*}', $parameter), $this->path);
343+
}
344+
return $this;
345+
}
346+
```
347+
348+
Example Usage:
349+
350+
```php
351+
$route = (new Route('profile', '/profile/{username}/{id}'))->whereTwoSegments('username', 'id');
352+
```
353+
354+
#### Method `whereAnything()`
355+
356+
This method applies a constraint to match any characters.
357+
358+
```php
359+
/**
360+
* Sets a constraint to match any characters.
361+
*
362+
* Example: /{anyPath}
363+
*
364+
* @param mixed ...$parameters The route parameters to apply the constraint to.
365+
* @return self The updated Route instance.
366+
*/
367+
public function whereAnything(...$parameters): self
368+
{
369+
$this->assignExprToParameters($parameters, '.+');
370+
foreach ($parameters as $parameter) {
371+
$this->path = str_replace(sprintf('{%s}', $parameter), sprintf('{%s*}', $parameter), $this->path);
372+
}
373+
return $this;
374+
}
375+
```
376+
377+
Example Usage:
378+
379+
```php
380+
$route = (new Route('any', '/{anyPath}'))->whereAnything('anyPath');
381+
```
382+
383+
#### Method `whereDate()`
384+
385+
This method applies a date constraint to the specified route parameters, expecting a format `YYYY-MM-DD`.
386+
387+
```php
388+
/**
389+
* Sets a date constraint on the specified route parameters.
390+
*
391+
* Example: /{date}
392+
*
393+
* @param mixed ...$parameters The route parameters to apply the constraint to.
394+
* @return self The updated Route instance.
395+
*/
396+
public function whereDate(...$parameters): self
397+
{
398+
$this->assignExprToParameters($parameters, '\d{4}-\d{2}-\d{2}');
399+
return $this;
400+
}
401+
```
402+
403+
Example Usage:
404+
405+
```php
406+
$route = (new Route('date', '/date/{date}'))->whereDate('date');
407+
```
408+
409+
#### Method `whereYearMonth()`
410+
411+
This method applies a year-month constraint to the specified route parameters, expecting a format `YYYY-MM`.
412+
413+
```php
414+
/**
415+
* Sets a year/month constraint on the specified route parameters.
416+
*
417+
* Example: /{yearMonth}
418+
*
419+
* @param mixed ...$parameters The route parameters to apply the constraint to.
420+
* @return self The updated Route instance.
421+
*/
422+
public function whereYearMonth(...$parameters): self
423+
{
424+
$this->assignExprToParameters($parameters, '\d{4}-\d{2}');
425+
return $this;
426+
}
427+
```
428+
429+
Example Usage:
430+
431+
```php
432+
$route = (new Route('yearMonth', '/yearMonth/{yearMonth}'))->whereYearMonth('yearMonth');
433+
```
434+
435+
#### Method `whereEmail()`
436+
437+
This method applies an email constraint to the specified route parameters.
438+
439+
```php
440+
/**
441+
* Sets an email constraint on the specified route parameters.
442+
*
443+
* Example: /{email}
444+
*
445+
* @param mixed ...$parameters The route parameters to apply the constraint to.
446+
* @return self The updated Route instance.
447+
*/
448+
public function whereEmail(...$parameters): self
449+
{
450+
$this->assignExprToParameters($parameters, '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}');
451+
return $this;
452+
}
453+
```
454+
455+
Example Usage:
456+
457+
```php
458+
$route = (new Route('user', '/user/{email}'))->whereEmail('email');
459+
```
460+
461+
#### Method `whereUuid()`
462+
463+
This method applies a UUID constraint to the specified route parameters.
464+
465+
```php
466+
/**
467+
* Sets a UUID constraint on the specified route parameters.
468+
*
469+
* Example: /{uuid}
470+
*
471+
* @param mixed ...$parameters The route parameters to apply the constraint to.
472+
* @return self The updated Route instance.
473+
*/
474+
public function whereUuid(...$parameters): self
475+
{
476+
$this->assignExprToParameters($parameters, '[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}');
477+
return $this;
478+
}
479+
```
480+
481+
Example Usage:
482+
483+
```php
484+
$route = (new Route('profile', '/profile/{uuid}'))->whereUuid('uuid');
485+
```
486+
487+
#### Method `whereBool()`
488+
489+
This method applies a boolean constraint to the specified route parameters, accepting `true`, `false`, `1`, and `0`.
490+
491+
```php
492+
/**
493+
* Sets a boolean constraint on the specified route parameters.
494+
*
495+
* Example: /{isActive}
496+
*
497+
* @param mixed ...$parameters The route parameters to apply the constraint to.
498+
* @return self The updated Route instance.
499+
*/
500+
public function whereBool(...$parameters): self
501+
{
502+
$this->assignExprToParameters($parameters, 'true|false|1|0');
503+
return $this;
504+
}
505+
```
506+
507+
Example Usage:
508+
509+
```php
510+
$route = (new Route('status', '/status/{isActive}'))->whereBool('isActive');
511+
```
512+
327513
#### Method `where()`
328514

329515
This method allows you to define a custom constraint on a specified route parameter.

src/Route.php

+65-3
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,20 @@ public function __construct(string $name, string $path, $handler, array $methods
7979
public function match(string $path): bool
8080
{
8181
$regex = $this->getPath();
82+
// This loop replaces all route variables like {var} or {var*} with corresponding regex patterns.
83+
// If the variable name ends with '*', it means the value can contain slashes (e.g. /foo/bar).
84+
// In that case, we use a permissive regex: (?P<varName>.+) — matches everything including slashes.
85+
// Otherwise, we use a strict regex: (?P<varName>[^/]++), which excludes slashes for standard segments.
86+
// The possessive quantifier '++' is used for better performance (avoids unnecessary backtracking).
8287
foreach ($this->getVarsNames() as $variable) {
8388
$varName = trim($variable, '{\}');
84-
$regex = str_replace($variable, '(?P<' . $varName . '>[^/]++)', $regex);
89+
$end = '*';
90+
if ((@substr_compare($varName, $end, -strlen($end)) == 0)) {
91+
$varName = rtrim($varName, $end);
92+
$regex = str_replace($variable, '(?P<' . $varName . '>.+)', $regex); // allows slashes
93+
continue;
94+
}
95+
$regex = str_replace($variable, '(?P<' . $varName . '>[^/]++)', $regex); // faster, excludes slashes
8596
}
8697

8798
if (!preg_match('#^' . $regex . '$#sD', Helper::trimPath($path), $matches)) {
@@ -93,8 +104,13 @@ public function match(string $path): bool
93104
}, ARRAY_FILTER_USE_KEY);
94105

95106
foreach ($values as $key => $value) {
96-
if (array_key_exists($key, $this->wheres) && !preg_match('/^'.$this->wheres[$key].'$/', $value)) {
97-
return false;
107+
if (array_key_exists($key, $this->wheres)) {
108+
$pattern = $this->wheres[$key];
109+
$delimiter = '#';
110+
$regex = $delimiter . '^' . $pattern . '$' . $delimiter;
111+
if (!preg_match($regex, $value)) {
112+
return false;
113+
}
98114
}
99115
$this->attributes[$key] = $value;
100116
}
@@ -204,6 +220,52 @@ public function whereAlpha(...$parameters): self
204220
return $this;
205221
}
206222

223+
public function whereTwoSegments(...$parameters): self
224+
{
225+
$this->assignExprToParameters($parameters, '[a-zA-Z0-9\-_]+/[a-zA-Z0-9\-_]+');
226+
foreach ($parameters as $parameter) {
227+
$this->path = str_replace(sprintf('{%s}', $parameter), sprintf('{%s*}', $parameter), $this->path);
228+
}
229+
return $this;
230+
}
231+
232+
public function whereAnything(string $parameter): self
233+
{
234+
$this->assignExprToParameters([$parameter], '.+');
235+
$this->path = str_replace(sprintf('{%s}', $parameter), sprintf('{%s*}', $parameter), $this->path);
236+
return $this;
237+
}
238+
239+
public function whereDate(...$parameters): self
240+
{
241+
$this->assignExprToParameters($parameters, '\d{4}-\d{2}-\d{2}');
242+
return $this;
243+
}
244+
245+
public function whereYearMonth(...$parameters): self
246+
{
247+
$this->assignExprToParameters($parameters, '\d{4}-\d{2}');
248+
return $this;
249+
}
250+
251+
public function whereEmail(...$parameters): self
252+
{
253+
$this->assignExprToParameters($parameters, '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}');
254+
return $this;
255+
}
256+
257+
public function whereUuid(...$parameters): self
258+
{
259+
$this->assignExprToParameters($parameters, '[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}');
260+
return $this;
261+
}
262+
263+
public function whereBool(...$parameters): self
264+
{
265+
$this->assignExprToParameters($parameters, 'true|false|1|0');
266+
return $this;
267+
}
268+
207269
/**
208270
* Sets a custom constraint on the specified route parameter.
209271
*

0 commit comments

Comments
 (0)