Skip to content

Commit 74c5293

Browse files
committed
Ending trail/slash feature
- Feature: added support for slash in parameters (see readme). - Route: Fixed hardcoded param modifier. - Route: optimisations. - Updated Readme.
1 parent 5ab5087 commit 74c5293

File tree

8 files changed

+140
-36
lines changed

8 files changed

+140
-36
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
composer.lock
22
vendor/
33
.idea/
4-
.phpunit.result.cache
4+
.phpunit.result.cache
5+
tests/tmp

README.md

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,12 @@ You can donate any amount of your choice by [clicking here](https://www.paypal.c
3636
- [Available methods](#available-methods)
3737
- [Multiple HTTP-verbs](#multiple-http-verbs)
3838
- [Route parameters](#route-parameters)
39-
- [Required parameters](#required-parameters)
40-
- [Optional parameters](#optional-parameters)
41-
- [Regular expression constraints](#regular-expression-constraints)
42-
- [Regular expression route-match](#regular-expression-route-match)
43-
- [Custom regex for matching parameters](#custom-regex-for-matching-parameters)
39+
- [Required parameters](#required-parameters)
40+
- [Optional parameters](#optional-parameters)
41+
- [Including slash in parameters](#including-slash-in-parameters)
42+
- [Regular expression constraints](#regular-expression-constraints)
43+
- [Regular expression route-match](#regular-expression-route-match)
44+
- [Custom regex for matching parameters](#custom-regex-for-matching-parameters)
4445
- [Named routes](#named-routes)
4546
- [Generating URLs To Named Routes](#generating-urls-to-named-routes)
4647
- [Router groups](#router-groups)
@@ -490,6 +491,28 @@ SimpleRouter::get('/user/{name?}', function ($name = 'Simon') {
490491
});
491492
```
492493

494+
### Including slash in parameters
495+
496+
If you're working with WebDAV services the url could mean the difference between a file and a folder.
497+
498+
For instance `/path` will be considered a file - whereas `/path/` will be considered a folder.
499+
500+
The router can add the ending slash for the last parameter in your route based on the path. So if `/path/` is requested the parameter will contain the value of `path/` and visa versa.
501+
502+
To ensure compatibility with older versions, this feature is disabled by default and has to be enabled by setting
503+
the `setSettings(['includeSlash' => true])` or by using setting `setSlashParameterEnabled(true)` for your route.
504+
505+
**Example**
506+
507+
```php
508+
SimpleRouter::get('/path/{fileOrFolder}', function ($fileOrFolder) {
509+
return $fileOrFolder;
510+
})->setSettings(['includeSlash' => true]);
511+
```
512+
513+
- Requesting `/path/file` will return the `$fileOrFolder` value: `file`.
514+
- Requesting `/path/folder/` will return the `$fileOrFolder` value: `folder/`.
515+
493516
### Regular expression constraints
494517

495518
You may constrain the format of your route parameters using the where method on a route instance. The where method accepts the name of the parameter and a regular expression defining how the parameter should be constrained:

src/Pecee/Http/Url.php

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,12 @@ class Url implements JsonSerializable
4242
*/
4343
private $path;
4444

45+
/**
46+
* Original path with no sanitization to ending slash
47+
* @var string|null
48+
*/
49+
private $originalPath;
50+
4551
/**
4652
* @var array
4753
*/
@@ -73,6 +79,7 @@ public function __construct(?string $url)
7379

7480
if (isset($data['path']) === true) {
7581
$this->setPath($data['path']);
82+
$this->originalPath = $data['path'];
7683
}
7784

7885
$this->fragment = $data['fragment'] ?? null;
@@ -226,6 +233,15 @@ public function getPath(): ?string
226233
return $this->path ?? '/';
227234
}
228235

236+
/**
237+
* Get original path with no sanitization of ending trail/slash.
238+
* @return string|null
239+
*/
240+
public function getOriginalPath(): ?string
241+
{
242+
return $this->originalPath;
243+
}
244+
229245
/**
230246
* Set the url path
231247
*
@@ -284,7 +300,7 @@ public function setQueryString(string $queryString): self
284300
$params = [];
285301
parse_str($queryString, $params);
286302

287-
if(count($params) > 0) {
303+
if (count($params) > 0) {
288304
return $this->setParams($params);
289305
}
290306

@@ -469,7 +485,7 @@ public function getRelativeUrl(bool $includeParams = true): string
469485
{
470486
$path = $this->path ?? '/';
471487

472-
if($includeParams === false) {
488+
if ($includeParams === false) {
473489
return $path;
474490
}
475491

src/Pecee/SimpleRouter/Route/Route.php

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@ abstract class Route implements IRoute
2020
*/
2121
protected $filterEmptyParams = true;
2222

23+
/**
24+
* If true the last parameter of the route will include ending trail/slash.
25+
* @var bool
26+
*/
27+
protected $slashParameterEnabled = false;
28+
2329
/**
2430
* Default regular expression used for parsing parameters.
2531
* @var string|null
@@ -111,7 +117,7 @@ public function renderRoute(Request $request, Router $router): ?string
111117
return $router->getClassLoader()->loadClassMethod($class, $method, $parameters);
112118
}
113119

114-
protected function parseParameters($route, $url, $parameterRegex = null): ?array
120+
protected function parseParameters($route, $url, Request $request, $parameterRegex = null): ?array
115121
{
116122
$regex = (strpos($route, $this->paramModifiers[0]) === false) ? null :
117123
sprintf
@@ -123,16 +129,18 @@ protected function parseParameters($route, $url, $parameterRegex = null): ?array
123129
);
124130

125131
// Ensures that host names/domains will work with parameters
126-
127-
if($route[0] == '{') $url = '/' . ltrim($url, '/');
132+
if ($route[0] === $this->paramModifiers[0]) {
133+
$url = '/' . ltrim($url, '/');
134+
}
135+
128136
$urlRegex = '';
129137
$parameters = [];
130138

131139
if ($regex === null || (bool)preg_match_all('/' . $regex . '/u', $route, $parameters) === false) {
132140
$urlRegex = preg_quote($route, '/');
133141
} else {
134142

135-
foreach (preg_split('/((\.?-?\/?){[^}]+})/', $route) as $key => $t) {
143+
foreach (preg_split('/((\.?-?\/?){[^' . $this->paramModifiers[1] . ']+' . $this->paramModifiers[1] . ')/', $route) as $key => $t) {
136144

137145
$regex = '';
138146

@@ -154,6 +162,17 @@ protected function parseParameters($route, $url, $parameterRegex = null): ?array
154162
}
155163
}
156164

165+
// Get name of last param
166+
/*$lastParam = null;
167+
$start = strrpos($route, '{');
168+
if($start > -1) {
169+
$param = substr($route, $start, strrpos($route, '}') + 1 - $start);
170+
if(str_ends_with($route, $param . '/')) {
171+
$lastParam = $param;
172+
}
173+
}*/
174+
175+
157176
if (trim($urlRegex) === '' || (bool)preg_match(sprintf($this->urlRegex, $urlRegex), $url, $matches) === false) {
158177
return null;
159178
}
@@ -167,18 +186,24 @@ protected function parseParameters($route, $url, $parameterRegex = null): ?array
167186
$lastParams = [];
168187

169188
/* Only take matched parameters with name */
170-
foreach ((array)$parameters[1] as $name) {
189+
$originalPath = $request->getUrl()->getOriginalPath();
190+
foreach ((array)$parameters[1] as $i => $name) {
171191

172192
// Ignore parent parameters
173193
if (isset($groupParameters[$name]) === true) {
174194
$lastParams[$name] = $matches[$name];
175195
continue;
176196
}
177197

198+
// If last parameter, use slash according to original path (non sanitized version)
199+
if ($this->slashParameterEnabled && ($i === count($parameters[1]) - 1) && str_ends_with($route, $this->paramModifiers[0] . $name . $this->paramModifiers[1] . '/') && $originalPath[strlen($originalPath) - 1] === '/') {
200+
$matches[$name] .= '/';
201+
}
202+
178203
$values[$name] = (isset($matches[$name]) === true && $matches[$name] !== '') ? $matches[$name] : null;
179204
}
180205

181-
$values = array_merge($values, $lastParams);
206+
$values += $lastParams;
182207
}
183208

184209
$this->originalParameters = $values;
@@ -387,6 +412,17 @@ public function getNamespace(): ?string
387412
return $this->namespace ?? $this->defaultNamespace;
388413
}
389414

415+
public function setSlashParameterEnabled(bool $enabled): self
416+
{
417+
$this->slashParameterEnabled = $enabled;
418+
return $this;
419+
}
420+
421+
public function getSlashParameterEnabled(): bool
422+
{
423+
return $this->slashParameterEnabled;
424+
}
425+
390426
/**
391427
* Export route settings to array so they can be merged with another route.
392428
*
@@ -416,6 +452,10 @@ public function toArray(): array
416452
$values['defaultParameterRegex'] = $this->defaultParameterRegex;
417453
}
418454

455+
if ($this->slashParameterEnabled === true) {
456+
$values['includeSlash'] = $this->slashParameterEnabled;
457+
}
458+
419459
return $values;
420460
}
421461

@@ -453,6 +493,10 @@ public function setSettings(array $settings, bool $merge = false): IRoute
453493
$this->setDefaultParameterRegex($settings['defaultParameterRegex']);
454494
}
455495

496+
if (isset($settings['includeSlash']) === true) {
497+
$this->setSlashParameterEnabled($settings['includeSlash']);
498+
}
499+
456500
return $this;
457501
}
458502

src/Pecee/SimpleRouter/Route/RouteGroup.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public function matchDomain(Request $request): bool
3333
return true;
3434
}
3535

36-
$parameters = $this->parseParameters($domain, $request->getHost(), '.*');
36+
$parameters = $this->parseParameters($domain, $request->getHost(), $request, '.*');
3737

3838
if ($parameters !== null && count($parameters) !== 0) {
3939
$this->parameters = $parameters;
@@ -60,7 +60,7 @@ public function matchRoute(string $url, Request $request): bool
6060

6161
if ($this->prefix !== null) {
6262
/* Parse parameters from current route */
63-
$parameters = $this->parseParameters($this->prefix, $url);
63+
$parameters = $this->parseParameters($this->prefix, $url, $request);
6464

6565
/* If no custom regular expression or parameters was found on this route, we stop */
6666
if ($parameters === null) {

src/Pecee/SimpleRouter/Route/RouteResource.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ public function matchRoute(string $url, Request $request): bool
9999
$route = rtrim($this->url, '/') . '/{id?}/{action?}';
100100

101101
/* Parse parameters from current route */
102-
$this->parameters = $this->parseParameters($route, $url);
102+
$this->parameters = $this->parseParameters($route, $url, $request);
103103

104104
/* If no custom regular expression or parameters was found on this route, we stop */
105105
if ($regexMatch === null && $this->parameters === null) {

src/Pecee/SimpleRouter/Route/RouteUrl.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public function matchRoute(string $url, Request $request): bool
3131
}
3232

3333
/* Parse parameters from current route */
34-
$parameters = $this->parseParameters($this->url, $url);
34+
$parameters = $this->parseParameters($this->url, $url, $request);
3535

3636
/* If no custom regular expression or parameters was found on this route, we stop */
3737
if ($regexMatch === null && $parameters === null) {

0 commit comments

Comments
 (0)