Skip to content

Commit

Permalink
Ending trail/slash feature
Browse files Browse the repository at this point in the history
- Feature: added support for slash in parameters (see readme).
- Route: Fixed hardcoded param modifier.
- Route: optimisations.
- Updated Readme.
  • Loading branch information
skipperbent committed Mar 25, 2023
1 parent 5ab5087 commit 74c5293
Show file tree
Hide file tree
Showing 8 changed files with 140 additions and 36 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
composer.lock
vendor/
.idea/
.phpunit.result.cache
.phpunit.result.cache
tests/tmp
33 changes: 28 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,12 @@ You can donate any amount of your choice by [clicking here](https://www.paypal.c
- [Available methods](#available-methods)
- [Multiple HTTP-verbs](#multiple-http-verbs)
- [Route parameters](#route-parameters)
- [Required parameters](#required-parameters)
- [Optional parameters](#optional-parameters)
- [Regular expression constraints](#regular-expression-constraints)
- [Regular expression route-match](#regular-expression-route-match)
- [Custom regex for matching parameters](#custom-regex-for-matching-parameters)
- [Required parameters](#required-parameters)
- [Optional parameters](#optional-parameters)
- [Including slash in parameters](#including-slash-in-parameters)
- [Regular expression constraints](#regular-expression-constraints)
- [Regular expression route-match](#regular-expression-route-match)
- [Custom regex for matching parameters](#custom-regex-for-matching-parameters)
- [Named routes](#named-routes)
- [Generating URLs To Named Routes](#generating-urls-to-named-routes)
- [Router groups](#router-groups)
Expand Down Expand Up @@ -490,6 +491,28 @@ SimpleRouter::get('/user/{name?}', function ($name = 'Simon') {
});
```

### Including slash in parameters

If you're working with WebDAV services the url could mean the difference between a file and a folder.

For instance `/path` will be considered a file - whereas `/path/` will be considered a folder.

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.

To ensure compatibility with older versions, this feature is disabled by default and has to be enabled by setting
the `setSettings(['includeSlash' => true])` or by using setting `setSlashParameterEnabled(true)` for your route.

**Example**

```php
SimpleRouter::get('/path/{fileOrFolder}', function ($fileOrFolder) {
return $fileOrFolder;
})->setSettings(['includeSlash' => true]);
```

- Requesting `/path/file` will return the `$fileOrFolder` value: `file`.
- Requesting `/path/folder/` will return the `$fileOrFolder` value: `folder/`.

### Regular expression constraints

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:
Expand Down
20 changes: 18 additions & 2 deletions src/Pecee/Http/Url.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ class Url implements JsonSerializable
*/
private $path;

/**
* Original path with no sanitization to ending slash
* @var string|null
*/
private $originalPath;

/**
* @var array
*/
Expand Down Expand Up @@ -73,6 +79,7 @@ public function __construct(?string $url)

if (isset($data['path']) === true) {
$this->setPath($data['path']);
$this->originalPath = $data['path'];
}

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

/**
* Get original path with no sanitization of ending trail/slash.
* @return string|null
*/
public function getOriginalPath(): ?string
{
return $this->originalPath;
}

/**
* Set the url path
*
Expand Down Expand Up @@ -284,7 +300,7 @@ public function setQueryString(string $queryString): self
$params = [];
parse_str($queryString, $params);

if(count($params) > 0) {
if (count($params) > 0) {
return $this->setParams($params);
}

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

if($includeParams === false) {
if ($includeParams === false) {
return $path;
}

Expand Down
56 changes: 50 additions & 6 deletions src/Pecee/SimpleRouter/Route/Route.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ abstract class Route implements IRoute
*/
protected $filterEmptyParams = true;

/**
* If true the last parameter of the route will include ending trail/slash.
* @var bool
*/
protected $slashParameterEnabled = false;

/**
* Default regular expression used for parsing parameters.
* @var string|null
Expand Down Expand Up @@ -111,7 +117,7 @@ public function renderRoute(Request $request, Router $router): ?string
return $router->getClassLoader()->loadClassMethod($class, $method, $parameters);
}

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

// Ensures that host names/domains will work with parameters

if($route[0] == '{') $url = '/' . ltrim($url, '/');
if ($route[0] === $this->paramModifiers[0]) {
$url = '/' . ltrim($url, '/');
}

$urlRegex = '';
$parameters = [];

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

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

$regex = '';

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

// Get name of last param
/*$lastParam = null;
$start = strrpos($route, '{');
if($start > -1) {
$param = substr($route, $start, strrpos($route, '}') + 1 - $start);
if(str_ends_with($route, $param . '/')) {
$lastParam = $param;
}
}*/


if (trim($urlRegex) === '' || (bool)preg_match(sprintf($this->urlRegex, $urlRegex), $url, $matches) === false) {
return null;
}
Expand All @@ -167,18 +186,24 @@ protected function parseParameters($route, $url, $parameterRegex = null): ?array
$lastParams = [];

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

// Ignore parent parameters
if (isset($groupParameters[$name]) === true) {
$lastParams[$name] = $matches[$name];
continue;
}

// If last parameter, use slash according to original path (non sanitized version)
if ($this->slashParameterEnabled && ($i === count($parameters[1]) - 1) && str_ends_with($route, $this->paramModifiers[0] . $name . $this->paramModifiers[1] . '/') && $originalPath[strlen($originalPath) - 1] === '/') {
$matches[$name] .= '/';
}

$values[$name] = (isset($matches[$name]) === true && $matches[$name] !== '') ? $matches[$name] : null;
}

$values = array_merge($values, $lastParams);
$values += $lastParams;
}

$this->originalParameters = $values;
Expand Down Expand Up @@ -387,6 +412,17 @@ public function getNamespace(): ?string
return $this->namespace ?? $this->defaultNamespace;
}

public function setSlashParameterEnabled(bool $enabled): self
{
$this->slashParameterEnabled = $enabled;
return $this;
}

public function getSlashParameterEnabled(): bool
{
return $this->slashParameterEnabled;
}

/**
* Export route settings to array so they can be merged with another route.
*
Expand Down Expand Up @@ -416,6 +452,10 @@ public function toArray(): array
$values['defaultParameterRegex'] = $this->defaultParameterRegex;
}

if ($this->slashParameterEnabled === true) {
$values['includeSlash'] = $this->slashParameterEnabled;
}

return $values;
}

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

if (isset($settings['includeSlash']) === true) {
$this->setSlashParameterEnabled($settings['includeSlash']);
}

return $this;
}

Expand Down
4 changes: 2 additions & 2 deletions src/Pecee/SimpleRouter/Route/RouteGroup.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public function matchDomain(Request $request): bool
return true;
}

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

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

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

/* If no custom regular expression or parameters was found on this route, we stop */
if ($parameters === null) {
Expand Down
2 changes: 1 addition & 1 deletion src/Pecee/SimpleRouter/Route/RouteResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ public function matchRoute(string $url, Request $request): bool
$route = rtrim($this->url, '/') . '/{id?}/{action?}';

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

/* If no custom regular expression or parameters was found on this route, we stop */
if ($regexMatch === null && $this->parameters === null) {
Expand Down
2 changes: 1 addition & 1 deletion src/Pecee/SimpleRouter/Route/RouteUrl.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public function matchRoute(string $url, Request $request): bool
}

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

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

0 comments on commit 74c5293

Please sign in to comment.