Skip to content

Commit 8be42ca

Browse files
authored
Merge pull request #638 from skipperbent/v5-feature-ending-slash
[FEATURE] Parameter ending trail/slash
2 parents 5ab5087 + 4a73609 commit 8be42ca

File tree

8 files changed

+131
-36
lines changed

8 files changed

+131
-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: 41 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,7 @@ protected function parseParameters($route, $url, $parameterRegex = null): ?array
154162
}
155163
}
156164

165+
// Get name of last param
157166
if (trim($urlRegex) === '' || (bool)preg_match(sprintf($this->urlRegex, $urlRegex), $url, $matches) === false) {
158167
return null;
159168
}
@@ -167,18 +176,25 @@ protected function parseParameters($route, $url, $parameterRegex = null): ?array
167176
$lastParams = [];
168177

169178
/* Only take matched parameters with name */
170-
foreach ((array)$parameters[1] as $name) {
179+
$originalPath = $request->getUrl()->getOriginalPath();
180+
foreach ((array)$parameters[1] as $i => $name) {
171181

172182
// Ignore parent parameters
173183
if (isset($groupParameters[$name]) === true) {
174184
$lastParams[$name] = $matches[$name];
175185
continue;
176186
}
177187

188+
// If last parameter and slash parameter is enabled, use slash according to original path (non sanitized version)
189+
$lastParameter = $this->paramModifiers[0] . $name . $this->paramModifiers[1] . '/';
190+
if ($this->slashParameterEnabled && ($i === count($parameters[1]) - 1) && (substr_compare($route, $lastParameter, -strlen($lastParameter)) === 0) && $originalPath[strlen($originalPath) - 1] === '/') {
191+
$matches[$name] .= '/';
192+
}
193+
178194
$values[$name] = (isset($matches[$name]) === true && $matches[$name] !== '') ? $matches[$name] : null;
179195
}
180196

181-
$values = array_merge($values, $lastParams);
197+
$values += $lastParams;
182198
}
183199

184200
$this->originalParameters = $values;
@@ -387,6 +403,17 @@ public function getNamespace(): ?string
387403
return $this->namespace ?? $this->defaultNamespace;
388404
}
389405

406+
public function setSlashParameterEnabled(bool $enabled): self
407+
{
408+
$this->slashParameterEnabled = $enabled;
409+
return $this;
410+
}
411+
412+
public function getSlashParameterEnabled(): bool
413+
{
414+
return $this->slashParameterEnabled;
415+
}
416+
390417
/**
391418
* Export route settings to array so they can be merged with another route.
392419
*
@@ -416,6 +443,10 @@ public function toArray(): array
416443
$values['defaultParameterRegex'] = $this->defaultParameterRegex;
417444
}
418445

446+
if ($this->slashParameterEnabled === true) {
447+
$values['includeSlash'] = $this->slashParameterEnabled;
448+
}
449+
419450
return $values;
420451
}
421452

@@ -453,6 +484,10 @@ public function setSettings(array $settings, bool $merge = false): IRoute
453484
$this->setDefaultParameterRegex($settings['defaultParameterRegex']);
454485
}
455486

487+
if (isset($settings['includeSlash']) === true) {
488+
$this->setSlashParameterEnabled($settings['includeSlash']);
489+
}
490+
456491
return $this;
457492
}
458493

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)