Compare commits

...

58 Commits

Author SHA1 Message Date
Simon Sessingø 5dbfc3dbfe Merge pull request #639 from skipperbent/v5-development
Version 5.1.0.0
2023-03-25 03:19:13 +01:00
Simon Sessingø 8be42ca1a6 Merge pull request #638 from skipperbent/v5-feature-ending-slash
[FEATURE] Parameter ending trail/slash
2023-03-25 03:13:28 +01:00
sessingo 4a7360909c Fixed for PHP7 2023-03-25 02:24:19 +01:00
sessingo 74c52931e9 Ending trail/slash feature
- Feature: added support for slash in parameters (see readme).
- Route: Fixed hardcoded param modifier.
- Route: optimisations.
- Updated Readme.
2023-03-25 02:20:06 +01:00
Simon Sessingø 0aea8673d9 Merge pull request #632 from skipperbent/v5-development
Version 5.0.0.3
2023-02-13 14:06:24 +01:00
sessingo 5ab5087f8e Fixed CSRF-token not triggering exception handlers. 2023-02-13 13:59:49 +01:00
sessingo b7c1b52a57 Updated phpstan 2023-02-12 23:39:49 +01:00
Simon Sessingø 9c66a4dfd8 Merge pull request #630 from skipperbent/v5-development
Version 5.0.0.2
2023-02-11 17:34:48 +01:00
sessingo 941149d8d7 Fixed deprication warnings 2023-02-11 17:31:00 +01:00
sessingo 1764b67112 Updated composer.json 2023-02-11 17:25:54 +01:00
sessingo 3742998537 Updated git workflows 2023-02-11 17:22:40 +01:00
sessingo 20e00efbed Php8 deprication warning 2023-02-11 17:16:40 +01:00
sessingo 7dd176a771 Fixed strtolower php8 deprication warning 2023-02-10 22:47:45 +01:00
sessingo abda9d468b Fixed deprication message when using response()->json 2023-02-10 08:48:58 +01:00
sessingo 23a29ce5d1 Fixed php strtolower deprication warning 2023-02-10 06:10:29 +01:00
Simon Sessingø d5bf77cbd4 Merge pull request #628 from skipperbent/v5-development
Fixed offsetGet return type deprication warning
2023-02-09 03:34:48 +01:00
sessingo e34fe47a04 Fixed offsetGet return type deprication warning 2023-02-09 03:29:41 +01:00
Simon Sessingø 1e9fa9c6a1 Merge pull request #627 from skipperbent/v4-development
Version 5.0.0.0
2023-02-09 03:07:54 +01:00
sessingo f0a4b6e46f Updated composer 2023-02-09 03:01:17 +01:00
Simon Sessingø e38a406957 Merge pull request #594 from redoonetworks/dymanic-domain
Dynamic domain, fixed Subdomain
2023-02-09 02:32:18 +01:00
Simon Sessingø 0630569f56 Merge pull request #592 from skipperbent/master
fixed json_encode 2nd parameter int flag issue in response and issue with offsetGet return type incompatibility
2023-02-09 02:30:02 +01:00
Simon Sessingø b82e29c864 Merge pull request #614 from DeveloperMarius/non-ascii-chars-urlencoding
urlencoding issue with non-ASCII chars in request-uri header
2023-02-09 02:29:25 +01:00
Simon Sessingø 9c79901316 Merge pull request #604 from xJuvi/xJuvi-patch-1
Make current processing rule accessible
2023-02-09 02:28:34 +01:00
Simon Sessingø fbc87cc9bd Merge pull request #589 from mauroagr/patch-1
Update in error status code
2023-02-09 02:26:36 +01:00
DeveloperMarius 301c2cfe4a fixed urldecode request-uri header 2022-03-01 15:39:31 +01:00
Hannes 01bad94af0 Update Router.php 2022-02-02 15:20:23 +01:00
Hannes a1d5f38af7 Add function to fetch currect processing route 2022-01-02 21:59:39 +01:00
Hannes b5e42dbdfb Make current processing rule accessible
Adds an  constant with the data from the current processing rule to make it globally accessible, f.e. in middleware objects
2022-01-02 14:42:43 +01:00
Stefan Warnat 06a63eb0e7 Fix Typo 2021-10-04 02:02:45 +02:00
Stefan Warnat 5268a998ff Extend domain filter to support fixed subdomain and domain parameter 2021-10-04 01:49:42 +02:00
Stefan Warnat 9fa7ad3e91 implement test Function to test output without reset 2021-10-04 01:49:00 +02:00
Mauro Tschiedel 0097725ef2 Update in error status code
Add example If you will add specific status code for the browser
2021-09-01 08:22:12 -03:00
Simon Sessingø 749f252ffb Merge pull request #584 from skipperbent/v4-release
V4 release
2021-07-18 01:45:46 +02:00
Simon Sessingø 032a2ae7e0 Merge pull request #583 from skipperbent/v4-development
Version 4.3.7.2
2021-07-18 01:45:36 +02:00
Simon Sessingø c2e2d3bb5d Merge pull request #582 from skipperbent/v4-group-prefix-bug
Fixed issue causing group prefix to trigger on paths without "/" (issue #573 - thanks @Venloress)
2021-07-18 01:44:57 +02:00
Simon Sessingø 5dd0690009 Fixed issue causing group prefix to trigger on paths without "/" (issue #573 - thanks @Venloress). 2021-07-18 01:41:26 +02:00
Simon Sessingø dbcf8f19a3 Merge pull request #581 from skipperbent/v4-release
V4 release
2021-07-17 22:01:09 +02:00
Simon Sessingø ee61eda1e8 Merge pull request #580 from skipperbent/v4-development
Updated documentation
2021-07-17 22:00:22 +02:00
Simon Sessingø 471bbe137f Updated documentation 2021-07-17 21:59:23 +02:00
Simon Sessingø b08dea9da5 Merge pull request #579 from skipperbent/v4-release
V4 release
2021-07-17 21:57:11 +02:00
Simon Sessingø b17ba06a8c Merge pull request #578 from skipperbent/v4-development
Version 4.3.7.0
2021-07-17 21:56:47 +02:00
Simon Sessingø 69494265a5 Merge pull request #577 from skipperbent/v4-prevent-merge-attribute
Added group attribute to stop router from merging exception-handlers (issue: #573)
2021-07-17 21:54:07 +02:00
Simon Sessingø 0d8915b206 Fixed return type. 2021-07-17 21:52:00 +02:00
Simon Sessingø b54a25804a Added group attribute to stop router from merging exception-handlers (issue: #573)
- Added new mergeExceptionHandlers attribute to stop router from merging exception-handlers.
- RouteGroup: Added setMergeExceptionHandlers and getMergeExceptionHandlers methods.
- IRouteGroup: Added setMergeExceptionHandlers and getMergeExceptionHandlers method.
- Updated documentation to reflect changes.
- Added unit-tests.
2021-07-17 21:46:05 +02:00
Simon Sessingø e3145cc1ec Merge pull request #576 from skipperbent/v4-release
V4 release
2021-07-16 22:31:20 +02:00
Simon Sessingø 75ea58dd9c Merge pull request #569 from skipperbent/v4-release
V4 release
2021-06-15 10:16:37 +02:00
Simon Sessingø 470000ad05 Merge pull request #564 from skipperbent/v4-release
V4 release
2021-06-09 09:19:27 +02:00
Simon Sessingø 3ffe9c8c07 Merge pull request #560 from skipperbent/v4-release
V4 release
2021-05-20 15:19:12 +02:00
Simon Sessingø 44c2b99513 Merge pull request #557 from skipperbent/v4-release
V4 release
2021-05-19 22:05:56 +02:00
Simon Sessingø 8b9e43c99e Merge pull request #554 from skipperbent/v4-release
V4 release
2021-05-19 04:43:40 +02:00
Simon Sessingø c4a9918048 Merge pull request #550 from skipperbent/v4-release
V4 release
2021-05-19 00:45:48 +02:00
Simon Sessingø 63a9ec65cf Merge pull request #545 from skipperbent/v4-release
V4 release
2021-05-02 14:00:27 +02:00
Simon Sessingø 14d3577a6a Merge pull request #542 from skipperbent/v4-release
V4 release
2021-04-28 10:05:46 +02:00
Simon Sessingø 869c65f347 Merge pull request #539 from skipperbent/v4-release
V4 release
2021-04-28 03:42:42 +02:00
Simon Sessingø 4fc48b4420 Merge pull request #535 from skipperbent/v4-release
V4 release
2021-04-01 03:16:46 +02:00
Simon Sessingø bef3207fcd Merge pull request #533 from skipperbent/v4-release
V4 release
2021-04-01 02:38:10 +02:00
Simon Sessingø d7bdee1092 Merge pull request #529 from skipperbent/v4-release
V4 release
2021-03-29 22:31:18 +02:00
Simon Sessingø 8c5ed8410a Merge pull request #525 from skipperbent/v4-release
V4 release
2021-03-29 01:19:40 +02:00
20 changed files with 398 additions and 74 deletions
+2 -2
View File
@@ -17,10 +17,10 @@ jobs:
- ubuntu-latest
- windows-latest
php-version:
- 7.1
- 7.4
- 8.0
phpunit-version:
- 7.5.20
- 8.5.32
dependencies:
- lowest
- highest
+2 -1
View File
@@ -1,4 +1,5 @@
composer.lock
vendor/
.idea/
.phpunit.result.cache
.phpunit.result.cache
tests/tmp
+71 -6
View File
@@ -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)
@@ -62,6 +63,7 @@ You can donate any amount of your choice by [clicking here](https://www.paypal.c
- [ExceptionHandlers](#exceptionhandlers)
- [Handling 404, 403 and other errors](#handling-404-403-and-other-errors)
- [Using custom exception handlers](#using-custom-exception-handlers)
- [Prevent merge of parent exception-handlers](#prevent-merge-of-parent-exception-handlers)
- [Urls](#urls)
- [Get the current url](#get-the-current-url)
- [Get by name (single route)](#get-by-name-single-route)
@@ -489,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:
@@ -971,6 +995,12 @@ If you do not want a redirect, but want the error-page rendered on the current-u
$request->setRewriteCallback('ErrorController@notFound');
```
If you will set the correct status for the browser error use:
```php
SimpleRouter::response()->httpCode(404);
```
## Using custom exception handlers
This is a basic example of an ExceptionHandler implementation (please see "[Easily overwrite route about to be loaded](#easily-overwrite-route-about-to-be-loaded)" for examples on how to change callback).
@@ -1014,6 +1044,41 @@ class CustomExceptionHandler implements IExceptionHandler
}
```
You can add your custom exception-handler class to your group by using the `exceptionHandler` settings-attribute.
`exceptionHandler` can be either class-name or array of class-names.
```php
SimpleRouter::group(['exceptionHandler' => \Demo\Handlers\CustomExceptionHandler::class], function() {
// Your routes here
});
```
### Prevent merge of parent exception-handlers
By default the router will merge exception-handlers to any handlers provided by parent groups, and will be executed in the order of newest to oldest.
If you want your groups exception handler to be executed independently, you can add the `mergeExceptionHandlers` attribute and set it to `false`.
```php
SimpleRouter::group(['prefix' => '/', 'exceptionHandler' => \Demo\Handlers\FirstExceptionHandler::class, 'mergeExceptionHandlers' => false], function() {
SimpleRouter::group(['prefix' => '/admin', 'exceptionHandler' => \Demo\Handlers\SecondExceptionHandler::class], function() {
// Both SecondExceptionHandler and FirstExceptionHandler will trigger (in that order).
});
SimpleRouter::group(['prefix' => '/user', 'exceptionHandler' => \Demo\Handlers\SecondExceptionHandler::class, 'mergeExceptionHandlers' => false], function() {
// Only SecondExceptionHandler will trigger.
});
});
```
---
# Urls
@@ -2037,4 +2102,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
SOFTWARE.
+11 -6
View File
@@ -27,16 +27,16 @@
}
],
"require": {
"php": ">=7.1",
"php": ">=7.4",
"ext-json": "*"
},
"require-dev": {
"phpunit/phpunit": "^7",
"phpunit/phpunit": "^8",
"mockery/mockery": "^1",
"phpstan/phpstan": "^0",
"phpstan/phpstan-phpunit": "^0",
"phpstan/phpstan-deprecation-rules": "^0",
"phpstan/phpstan-strict-rules": "^0"
"phpstan/phpstan": "^1",
"phpstan/phpstan-phpunit": "^1",
"phpstan/phpstan-deprecation-rules": "^1",
"phpstan/phpstan-strict-rules": "^1"
},
"scripts": {
"test": [
@@ -47,5 +47,10 @@
"psr-4": {
"Pecee\\": "src/Pecee/"
}
},
"config": {
"allow-plugins": {
"ocramius/package-versions": true
}
}
}
+1 -1
View File
@@ -81,7 +81,7 @@ class InputItem implements ArrayAccess, IInputItem, IteratorAggregate
return isset($this->value[$offset]);
}
public function offsetGet($offset)
public function offsetGet($offset): ?self
{
if ($this->offsetExists($offset) === true) {
return $this->value[$offset];
+7 -2
View File
@@ -129,7 +129,12 @@ class Request
$this->setHost($this->getHeader('http-host'));
// Check if special IIS header exist, otherwise use default.
$this->setUrl(new Url($this->getFirstHeader(['unencoded-url', 'request-uri'])));
$url = $this->getHeader('unencoded-url');
if($url !== null){
$this->setUrl(new Url($url));
}else{
$this->setUrl(new Url(urldecode((string)$this->getHeader('request-uri'))));
}
$this->setContentType((string)$this->getHeader('content-type'));
$this->setMethod((string)($_POST[static::FORCE_METHOD_KEY] ?? $this->getHeader('request-method')));
$this->inputHandler = new InputHandler($this);
@@ -359,7 +364,7 @@ class Request
*/
public function isAjax(): bool
{
return (strtolower($this->getHeader('http-x-requested-with')) === 'xmlhttprequest');
return (strtolower((string)$this->getHeader('http-x-requested-with')) === 'xmlhttprequest');
}
/**
+2 -2
View File
@@ -87,11 +87,11 @@ class Response
/**
* Json encode
* @param array|JsonSerializable $value
* @param ?int $options JSON options Bitmask consisting of JSON_HEX_QUOT, JSON_HEX_TAG, JSON_HEX_AMP, JSON_HEX_APOS, JSON_NUMERIC_CHECK, JSON_PRETTY_PRINT, JSON_UNESCAPED_SLASHES, JSON_FORCE_OBJECT, JSON_PRESERVE_ZERO_FRACTION, JSON_UNESCAPED_UNICODE, JSON_PARTIAL_OUTPUT_ON_ERROR.
* @param int $options JSON options Bitmask consisting of JSON_HEX_QUOT, JSON_HEX_TAG, JSON_HEX_AMP, JSON_HEX_APOS, JSON_NUMERIC_CHECK, JSON_PRETTY_PRINT, JSON_UNESCAPED_SLASHES, JSON_FORCE_OBJECT, JSON_PRESERVE_ZERO_FRACTION, JSON_UNESCAPED_UNICODE, JSON_PARTIAL_OUTPUT_ON_ERROR.
* @param int $dept JSON debt.
* @throws InvalidArgumentException
*/
public function json($value, ?int $options = null, int $dept = 512): void
public function json($value, int $options = 0, int $dept = 512): void
{
if (($value instanceof JsonSerializable) === false && is_array($value) === false) {
throw new InvalidArgumentException('Invalid type for parameter "value". Must be of type array or object implementing the \JsonSerializable interface.');
+18 -2
View File
@@ -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
*/
@@ -73,6 +79,7 @@ class Url implements JsonSerializable
if (isset($data['path']) === true) {
$this->setPath($data['path']);
$this->originalPath = $data['path'];
}
$this->fragment = $data['fragment'] ?? null;
@@ -226,6 +233,15 @@ class Url implements JsonSerializable
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
*
@@ -284,7 +300,7 @@ class Url implements JsonSerializable
$params = [];
parse_str($queryString, $params);
if(count($params) > 0) {
if (count($params) > 0) {
return $this->setParams($params);
}
@@ -469,7 +485,7 @@ class Url implements JsonSerializable
{
$path = $this->path ?? '/';
if($includeParams === false) {
if ($includeParams === false) {
return $path;
}
@@ -31,6 +31,21 @@ interface IGroupRoute extends IRoute
*/
public function setExceptionHandlers(array $handlers): self;
/**
* Returns true if group should overwrite existing exception-handlers.
*
* @return bool
*/
public function getMergeExceptionHandlers(): bool;
/**
* When enabled group will overwrite any existing exception-handlers.
*
* @param bool $merge
* @return static
*/
public function setMergeExceptionHandlers(bool $merge): self;
/**
* Get exception-handlers for group
*
@@ -167,7 +167,7 @@ abstract class LoadableRoute extends Route implements ILoadableRoute
if (stripos($url, $param1) !== false || stripos($url, $param) !== false) {
/* Add parameter to the correct position */
$url = str_ireplace([sprintf($param1, $param), sprintf($param2, $param)], $value, $url);
$url = str_ireplace([sprintf($param1, $param), sprintf($param2, $param)], (string)$value, $url);
} else {
/* Parameter aren't recognized and will be appended at the end of the url */
$url .= $value . '/';
@@ -195,7 +195,7 @@ abstract class LoadableRoute extends Route implements ILoadableRoute
*/
public function hasName(string $name): bool
{
return strtolower($this->name) === strtolower($name);
return strtolower((string)$this->name) === strtolower((string)$name);
}
/**
+42 -6
View File
@@ -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
@@ -111,7 +117,7 @@ abstract class Route implements IRoute
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
@@ -123,7 +129,10 @@ abstract class Route implements IRoute
);
// Ensures that host names/domains will work with parameters
$url = '/' . ltrim($url, '/');
if ($route[0] === $this->paramModifiers[0]) {
$url = '/' . ltrim($url, '/');
}
$urlRegex = '';
$parameters = [];
@@ -131,7 +140,7 @@ abstract class Route implements IRoute
$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 = '';
@@ -146,13 +155,14 @@ abstract class Route implements IRoute
$regex = $parameterRegex ?? $this->defaultParameterRegex ?? static::PARAMETERS_DEFAULT_REGEX;
}
$regex = sprintf('((\/|-)(?P<%2$s>%3$s))%1$s', $parameters[2][$key], $name, $regex);
$regex = sprintf('((\/|-|\.)(?P<%2$s>%3$s))%1$s', $parameters[2][$key], $name, $regex);
}
$urlRegex .= preg_quote($t, '/') . $regex;
}
}
// Get name of last param
if (trim($urlRegex) === '' || (bool)preg_match(sprintf($this->urlRegex, $urlRegex), $url, $matches) === false) {
return null;
}
@@ -166,7 +176,8 @@ abstract class Route implements IRoute
$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) {
@@ -174,10 +185,16 @@ abstract class Route implements IRoute
continue;
}
// If last parameter and slash parameter is enabled, use slash according to original path (non sanitized version)
$lastParameter = $this->paramModifiers[0] . $name . $this->paramModifiers[1] . '/';
if ($this->slashParameterEnabled && ($i === count($parameters[1]) - 1) && (substr_compare($route, $lastParameter, -strlen($lastParameter)) === 0) && $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;
@@ -386,6 +403,17 @@ abstract class Route implements IRoute
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.
*
@@ -415,6 +443,10 @@ abstract class Route implements IRoute
$values['defaultParameterRegex'] = $this->defaultParameterRegex;
}
if ($this->slashParameterEnabled === true) {
$values['includeSlash'] = $this->slashParameterEnabled;
}
return $values;
}
@@ -452,6 +484,10 @@ abstract class Route implements IRoute
$this->setDefaultParameterRegex($settings['defaultParameterRegex']);
}
if (isset($settings['includeSlash']) === true) {
$this->setSlashParameterEnabled($settings['includeSlash']);
}
return $this;
}
@@ -81,7 +81,7 @@ class RouteController extends LoadableRoute implements IControllerRoute
$url .= '//' . $group->getDomains()[0];
}
$url .= '/' . trim($this->getUrl(), '/') . '/' . strtolower($method) . implode('/', $parameters);
$url .= '/' . trim($this->getUrl(), '/') . '/' . strtolower((string)$method) . implode('/', $parameters);
return '/' . trim($url, '/') . '/';
}
+33 -4
View File
@@ -12,6 +12,7 @@ class RouteGroup extends Route implements IGroupRoute
protected $name;
protected $domains = [];
protected $exceptionHandlers = [];
protected $mergeExceptionHandlers = true;
/**
* Method called to check if a domain matches
@@ -32,10 +33,11 @@ class RouteGroup extends Route implements IGroupRoute
return true;
}
$parameters = $this->parseParameters($domain, $request->getHost(), '.*');
$parameters = $this->parseParameters($domain, $request->getHost(), $request, '.*');
if ($parameters !== null && count($parameters) !== 0) {
$this->parameters = $parameters;
return true;
}
}
@@ -58,7 +60,7 @@ class RouteGroup extends Route implements IGroupRoute
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) {
@@ -72,11 +74,11 @@ class RouteGroup extends Route implements IGroupRoute
$parsedPrefix = $this->prefix;
foreach ($this->getParameters() as $parameter => $value) {
$parsedPrefix = str_ireplace('{' . $parameter . '}', $value, $parsedPrefix);
$parsedPrefix = str_ireplace('{' . $parameter . '}', (string)$value, (string)$parsedPrefix);
}
/* Skip if prefix doesn't match */
if ($this->prefix !== null && stripos($url, $parsedPrefix) === false) {
if ($this->prefix !== null && stripos($url, rtrim($parsedPrefix, '/') . '/') === false) {
return false;
}
@@ -174,6 +176,29 @@ class RouteGroup extends Route implements IGroupRoute
return $this->prefix;
}
/**
* When enabled group will overwrite any existing exception-handlers.
*
* @param bool $merge
* @return static
*/
public function setMergeExceptionHandlers(bool $merge): IGroupRoute
{
$this->mergeExceptionHandlers = $merge;
return $this;
}
/**
* Returns true if group should overwrite existing exception-handlers.
*
* @return bool
*/
public function getMergeExceptionHandlers(): bool
{
return $this->mergeExceptionHandlers;
}
/**
* Merge with information from another route.
*
@@ -187,6 +212,10 @@ class RouteGroup extends Route implements IGroupRoute
$this->setPrefix($settings['prefix'] . $this->prefix);
}
if (isset($settings['mergeExceptionHandlers']) === true) {
$this->setMergeExceptionHandlers($settings['mergeExceptionHandlers']);
}
if ($merge === false && isset($settings['exceptionHandler']) === true) {
$this->setExceptionHandlers((array)$settings['exceptionHandler']);
}
@@ -99,14 +99,14 @@ class RouteResource extends LoadableRoute implements IControllerRoute
$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) {
return false;
}
$action = strtolower(trim($this->parameters['action']));
$action = strtolower(trim((string)$this->parameters['action']));
$id = $this->parameters['id'];
// Remove action parameter
+1 -1
View File
@@ -31,7 +31,7 @@ class RouteUrl extends LoadableRoute
}
/* 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) {
+43 -15
View File
@@ -35,6 +35,12 @@ class Router
* @var bool
*/
protected $isProcessingRoute;
/**
* Defines all data from current processing route.
* @var ILoadableRoute
*/
protected $currentProcessingRoute;
/**
* All added routes
@@ -160,7 +166,7 @@ class Router
public function addRoute(IRoute $route): IRoute
{
$this->fireEvents(EventHandler::EVENT_ADD_ROUTE, [
'route' => $route,
'route' => $route,
'isSubRoute' => $this->isProcessingRoute,
]);
@@ -203,7 +209,7 @@ class Router
/**
* Process added routes.
*
* @param array $routes
* @param array|IRoute[] $routes
* @param IGroupRoute|null $group
* @throws NotFoundHttpException
*/
@@ -211,9 +217,6 @@ class Router
{
$this->debug('Processing routes');
// Loop through each route-request
$exceptionHandlers = [];
// Stop processing routes if no valid route is found.
if ($this->request->getRewriteRoute() === null && $this->request->getUrl()->getOriginalUrl() === '') {
$this->debug('Halted route-processing as no valid route was found');
@@ -223,7 +226,7 @@ class Router
$url = $this->request->getRewriteUrl() ?? $this->request->getUrl()->getPath();
/* @var $route IRoute */
// Loop through each route-request
foreach ($routes as $route) {
$this->debug('Processing route "%s"', get_class($route));
@@ -240,13 +243,22 @@ class Router
/* Add exception handlers */
if (count($route->getExceptionHandlers()) !== 0) {
/** @noinspection AdditionOperationOnArraysInspection */
$exceptionHandlers += $route->getExceptionHandlers();
if ($route->getMergeExceptionHandlers() === true) {
foreach ($route->getExceptionHandlers() as $handler) {
$this->exceptionHandlers[] = $handler;
}
} else {
$this->exceptionHandlers = $route->getExceptionHandlers();
}
}
/* Only render partial group if it matches */
if ($route instanceof IPartialGroupRoute === true) {
$this->renderAndProcess($route);
continue;
}
}
@@ -264,8 +276,6 @@ class Router
$this->processedRoutes[] = $route;
}
}
$this->exceptionHandlers = array_merge($exceptionHandlers, $this->exceptionHandlers);
}
/**
@@ -332,8 +342,12 @@ class Router
'csrfVerifier' => $this->csrfVerifier,
]);
/* Verify csrf token for request */
$this->csrfVerifier->handle($this->request);
try {
/* Verify csrf token for request */
$this->csrfVerifier->handle($this->request);
} catch(\Exception $e) {
$this->handleException($e);
}
}
$output = $this->routeRequest();
@@ -367,6 +381,9 @@ class Router
foreach ($this->processedRoutes as $key => $route) {
$this->debug('Matching route "%s"', get_class($route));
/* Add current processing route to constants */
$this->currentProcessingRoute = $route;
/* If the route matches */
if ($route->matchRoute($url, $this->request) === true) {
@@ -595,7 +612,7 @@ class Router
if (strpos($name, '@') !== false) {
[$controller, $method] = array_map('strtolower', explode('@', $name));
if ($controller === strtolower($route->getClass()) && $method === strtolower($route->getMethod())) {
if ($controller === strtolower((string)$route->getClass()) && $method === strtolower((string)$route->getMethod())) {
$this->debug('Found route "%s" by controller "%s" and method "%s"', $route->getUrl(), $controller, $method);
return $route;
@@ -679,7 +696,7 @@ class Router
->setParams($getParams);
}
if($name !== null) {
if ($name !== null) {
/* We try to find a match on the given name */
$route = $this->findRoute($name);
@@ -923,6 +940,16 @@ class Router
{
return $this->debugList;
}
/**
* Get the current processing route details.
*
* @return ILoadableRoute
*/
public function getCurrentProcessingRoute(): ILoadableRoute
{
return $this->currentProcessingRoute;
}
/**
* Changes the rendering behavior of the router.
@@ -942,7 +969,8 @@ class Router
public function addExceptionHandler(IExceptionHandler $handler): self
{
$this->exceptionHandlers[] = $handler;
return $this;
}
}
}
+30 -3
View File
@@ -33,9 +33,9 @@ class RouterRewriteTest extends \PHPUnit\Framework\TestCase
global $stack;
$stack = [];
TestRouter::group(['exceptionHandler' => [ExceptionHandlerFirst::class, ExceptionHandlerSecond::class]], function () use ($stack) {
TestRouter::group(['exceptionHandler' => [ExceptionHandlerFirst::class, ExceptionHandlerSecond::class]], function () {
TestRouter::group(['exceptionHandler' => ExceptionHandlerThird::class], function () use ($stack) {
TestRouter::group(['prefix' => '/test', 'exceptionHandler' => ExceptionHandlerThird::class], function () {
TestRouter::get('/my-path', 'DummyController@method1');
@@ -43,7 +43,7 @@ class RouterRewriteTest extends \PHPUnit\Framework\TestCase
});
try {
TestRouter::debug('/my-non-existing-path', 'get');
TestRouter::debug('/test/non-existing', 'get');
} catch (\ResponseException $e) {
}
@@ -58,6 +58,33 @@ class RouterRewriteTest extends \PHPUnit\Framework\TestCase
}
public function testStopMergeExceptionHandlers()
{
global $stack;
$stack = [];
TestRouter::group(['prefix' => '/', 'exceptionHandler' => ExceptionHandlerFirst::class], function () {
TestRouter::group(['prefix' => '/admin', 'exceptionHandler' => ExceptionHandlerSecond::class, 'mergeExceptionHandlers' => false], function () {
TestRouter::get('/my-path', 'DummyController@method1');
});
});
try {
TestRouter::debug('/admin/my-path-test', 'get');
} catch (\Pecee\SimpleRouter\Exceptions\NotFoundHttpException $e) {
}
$expectedStack = [
ExceptionHandlerSecond::class,
];
$this->assertEquals($expectedStack, $stack);
}
public function testRewriteExceptionMessage()
{
$this->expectException(\Pecee\SimpleRouter\Exceptions\NotFoundHttpException::class);
@@ -175,6 +175,70 @@ class RouterRouteTest extends \PHPUnit\Framework\TestCase
TestRouter::debug('/test', 'get');
$this->assertFalse($result);
}
public function testFixedSubdomainDynamicDomain()
{
TestRouter::request()->setHost('other.world.com');
$result = false;
TestRouter::group(['domain' => 'other.{domain}'], function () use (&$result) {
TestRouter::get('/test', function ($domain = null) use (&$result) {
$result = true;
});
});
TestRouter::debug('/test', 'get');
$this->assertTrue($result);
}
public function testFixedSubdomainDynamicDomainParameter()
{
TestRouter::request()->setHost('other.world.com');
$result = false;
TestRouter::group(['domain' => 'other.{domain}'], function () use (&$result) {
TestRouter::get('/test', 'DummyController@param');
TestRouter::get('/test/{key}', 'DummyController@param');
});
$response = TestRouter::debugOutputNoReset('/test', 'get');
$this->assertEquals('world.com', $response);
$response = TestRouter::debugOutput('/test/unittest', 'get');
$this->assertEquals('unittest, world.com', $response);
}
public function testWrongFixedSubdomainDynamicDomain()
{
TestRouter::request()->setHost('wrong.world.com');
$result = false;
TestRouter::group(['domain' => 'other.{domain}'], function () use (&$result) {
TestRouter::get('/test', function ($domain = null) use (&$result) {
$result = true;
});
});
try {
TestRouter::debug('/test', 'get');
} catch(\Exception $e) {
}
$this->assertFalse($result);
}
+38 -18
View File
@@ -27,6 +27,23 @@ class RouterUrlTest extends \PHPUnit\Framework\TestCase
TestRouter::router()->reset();
}
public function testLastParameterSlash()
{
TestRouter::get('/test/{param}', function ($param) {
return $param;
})->setSettings(['includeSlash' => true]);
// Test with ending /
$output = TestRouter::debugOutputNoReset('/test/param/');
$this->assertEquals($output, 'param/');
// Test without ending /
$output = TestRouter::debugOutputNoReset('/test/param');
$this->assertEquals($output, 'param');
TestRouter::router()->reset();
}
public function testUnicodeCharacters()
{
// Test spanish characters
@@ -191,7 +208,7 @@ class RouterUrlTest extends \PHPUnit\Framework\TestCase
$results = '';
TestRouter::get('/tester/{param}', function ($param = null) use($results) {
TestRouter::get('/tester/{param}', function ($param = null) use ($results) {
return $results = $param;
})->setMatch('/(.*)/i');
@@ -234,9 +251,9 @@ class RouterUrlTest extends \PHPUnit\Framework\TestCase
TestRouter::debug('/');
$this->assertCount(2, $result);
$this->assertCount(2, $result);
}
public function testDefaultNamespace()
{
TestRouter::setDefaultNamespace('\\Default\\Namespace');
@@ -245,14 +262,14 @@ class RouterUrlTest extends \PHPUnit\Framework\TestCase
TestRouter::group([
'namespace' => 'Appended\Namespace',
'prefix' => '/horses',
'prefix' => '/horses',
], function () {
TestRouter::get('/', 'DummyController@method1');
TestRouter::group([
'namespace' => '\\New\\Namespace',
'prefix' => '/race',
'prefix' => '/race',
], function () {
TestRouter::get('/', 'DummyController@method1');
@@ -287,13 +304,14 @@ class RouterUrlTest extends \PHPUnit\Framework\TestCase
TestRouter::router()->reset();
}
public function testGroupPrefix() {
public function testGroupPrefix()
{
$result = false;
TestRouter::group(['prefix' => '/lang/{lang}'], function () use(&$result) {
TestRouter::group(['prefix' => '/lang/{lang}'], function () use (&$result) {
TestRouter::get('/test', function() use(&$result) {
TestRouter::get('/test', function () use (&$result) {
$result = true;
});
});
@@ -307,13 +325,13 @@ class RouterUrlTest extends \PHPUnit\Framework\TestCase
$result = null;
$expectedResult = 28;
TestRouter::group(['prefix' => '/lang/{lang}'], function () use(&$result) {
TestRouter::group(['prefix' => '/lang/{lang}'], function () use (&$result) {
TestRouter::get('/horse/{horseType}', function($horseType) use(&$result) {
TestRouter::get('/horse/{horseType}', function ($horseType) use (&$result) {
$result = false;
});
TestRouter::get('/user/{userId}', function($userId) use(&$result) {
TestRouter::get('/user/{userId}', function ($userId) use (&$result) {
$result = $userId;
});
});
@@ -324,14 +342,15 @@ class RouterUrlTest extends \PHPUnit\Framework\TestCase
}
public function testPassParameter() {
public function testPassParameter()
{
$result = false;
$expectedLanguage = 'da';
TestRouter::group(['prefix' => '/lang/{lang}'], function ($language) use(&$result) {
TestRouter::group(['prefix' => '/lang/{lang}'], function ($language) use (&$result) {
TestRouter::get('/test', function($language) use(&$result) {
TestRouter::get('/test', function ($language) use (&$result) {
$result = $language;
});
@@ -343,15 +362,16 @@ class RouterUrlTest extends \PHPUnit\Framework\TestCase
}
public function testPassParameterDeep() {
public function testPassParameterDeep()
{
$result = false;
$expectedLanguage = 'da';
TestRouter::group(['prefix' => '/lang/{lang}'], function ($language) use(&$result) {
TestRouter::group(['prefix' => '/lang/{lang}'], function ($language) use (&$result) {
TestRouter::group(['prefix' => '/admin'], function($language) use(&$result) {
TestRouter::get('/test', function($language) use(&$result) {
TestRouter::group(['prefix' => '/admin'], function ($language) use (&$result) {
TestRouter::get('/test', function ($language) use (&$result) {
$result = $language;
});
});
+13
View File
@@ -48,4 +48,17 @@ class TestRouter extends \Pecee\SimpleRouter\SimpleRouter
return $response;
}
public static function debugOutputNoReset(string $testUrl, string $testMethod = 'get', bool $reset = true): string
{
$response = null;
// Route request
ob_start();
static::debugNoReset($testUrl, $testMethod, $reset);
$response = ob_get_clean();
// Return response
return $response;
}
}