Compare commits

...

80 Commits

Author SHA1 Message Date
Simon 301d551981 Fix host url 2024-11-10 23:20:08 +01:00
Simon 90418eb41c Bugfixes
- Fixed host not set on request url.
- Url returns relativeUrl when calling toString() to avoid any issues when using in RouteUrl(url()) for rewrites.
2024-11-06 11:05:43 +01:00
Simon 0634ba79dc Fixed getUrl() not working for home urls. 2023-12-11 22:45:14 +01:00
Simon 3534233a76 Fixed included triggering on other request-types than post 2023-12-09 18:39:33 +01:00
Simon 0f55480156 Fixed rewrite-route not being executed in rare instances 2023-12-09 05:36:30 +01:00
Simon 99ed44eb1e Fixed setHost nullable value 2023-11-29 01:20:00 +01:00
Simon 565a926bd3 Remove port from hostname 2023-11-27 08:06:35 +01:00
Simon 64483652ff Strip any potential port number from hostname 2023-11-27 07:58:49 +01:00
Simon d17ee96221 Added better handling of domains on urls. 2023-11-27 06:53:33 +01:00
Simon ed1ed43484 - Fixed domains not being prepending properly to urls.
- Won't prepend subdomain to urls if subdomain is equal to current host.
- Url: implemented parse function.
- Router: getUrl now parses url to properly parse the subdomain.
2023-11-27 05:52:10 +01:00
Simon a275366a90 Fixed inputhandler overwriting post-values 2023-11-21 22:25:06 +01:00
Simon Sessingø 4d1caddce4 Merge pull request #683 from ms-afk/bugfix-rewrite-route-executed-twice
Fixed rare double execution of rewrite routes in exception handler
2023-11-21 16:11:56 +01:00
Simon Sessingø 0970bd00c6 Merge pull request #682 from ms-afk/fix-readme-php-di
Fixed the php-di integration example in the README
2023-11-21 16:11:06 +01:00
Simon Sessingø 49b132da93 Merge pull request #672 from ATC-4K/patch-1
Added @return never to Response.php
2023-11-21 16:10:16 +01:00
Simon 08d78c8f71 Added support for input stream when not json encoded 2023-11-21 16:08:22 +01:00
ms-afk 5986dc9a08 fixed rare double execution of rewrite routes in exception handler
If a rewrite route is present, Router's method handleException will, currently, be adding that route to the processedRoutes array without removing the hasPendingRewrite flag. This leads to the associated callback being executed twice if the callback itself returns NULL. This happens because the handleRouteRewrite method, finding that hasPendingRewrite is still set to true, adds the rewriteRoute to the processedRoutes for a second time, before finally setting that flag to false.
2023-10-03 12:26:41 +02:00
Marco Scagnol cdf165d0f4 Fixed the php-di integration example
The previous version of the example in the README used exceptions that were not imported (NotFoundHttpException) to wrap existing exceptions. I've adapted the example to be similar to the current version of Pecee\SimpleRouter\ClassLoader\ClassLoader. That includes returning strings from loadClassMethod and loadClosure and not wrapping every exception, thrown by the called functions, into NotFoundHttpException exceptions, as well as using the exception ClassNotFoundHttpException when a class cannot be found, instead of the generic NotFoundHttpException. I also changed the way the ClassLoader checks if the container can resolve the class by using the container's method "has" instead of the php function class_exists.
2023-09-30 16:22:52 +02:00
ATC-4K adfe70f191 Added @return never to Response.php
In PHP8.1 instead of :void :never would be returned.
As this project is PHP7.4 compatible, we add it as a PHPDOC, so IDEs using this project will no longer complain and automatic checks will successfully detect dead code after calling a redirect()
2023-07-10 21:16:32 +02:00
Simon Sessingø cd891d5334 Merge pull request #671 from skipperbent/v5-release
V5 release
2023-05-06 21:14:29 +02:00
Simon Sessingø 7feb464af1 Merge pull request #670 from skipperbent/v5-development
Version 5.3.0.5
2023-05-06 21:11:12 +02:00
sessingo d3b1577095 RouteResource: simplified the findUrl method (issue: #666) 2023-05-06 21:07:58 +02:00
Simon Sessingø 12b6e3c1ab Merge pull request #669 from skipperbent/v5-release
V5 release
2023-05-06 17:39:41 +02:00
Simon Sessingø f085134ae3 Merge pull request #668 from skipperbent/v5-development
Version 5.3.0.4
2023-05-06 17:37:00 +02:00
Simon Sessingø 00d1c534de Merge pull request #667 from skipperbent/fix-resource-parameters
Resource-type not respecting parameters when using getUrl (issue: #666)
2023-05-06 17:33:53 +02:00
Simon Sessingø 37f826f24c Merge pull request #664 from SunflowerFuchs/v5-development
Fixed Route::setUrl() behavior
2023-05-06 17:32:57 +02:00
sessingo f3c6015a59 Fixed Resource-type not respecting parameters when using getUrl (issue: #666) 2023-05-06 17:29:33 +02:00
Pascal Pirschel b2851e41f1 Fixed Route::setUrl() behavior
When there are no parameters in the url, correctly empty the routes parameter array
2023-05-05 13:48:26 +02:00
Simon Sessingø 8ffa1088ab Merge pull request #662 from skipperbent/v5-release
V5 release
2023-04-24 20:08:29 +02:00
Simon Sessingø 9b8843aa08 Merge pull request #661 from skipperbent/v5-development
Reverted exception handling to old behavior (issue: #660)
2023-04-24 20:08:17 +02:00
sessingo e105f266e3 Fixed typo 2023-04-24 20:05:33 +02:00
sessingo a49d7c13b6 Reverted exception handling to old behavior (issue: #660) 2023-04-24 20:03:15 +02:00
Simon Sessingø f565014dff Merge pull request #659 from skipperbent/v5-release
V5 release
2023-04-21 11:38:08 +02:00
Simon Sessingø ad765b9856 Merge pull request #658 from skipperbent/v5-development
InputItem: php8.1 deprecated warning
2023-04-21 11:35:24 +02:00
sessingo 4778a8f29e InputItem: php8.1 deprecated warning-Added returnTypeWillChange to offsetGet 2023-04-21 11:28:19 +02:00
Simon Sessingø 97b61fb8bf Merge pull request #656 from skipperbent/v5-release
V5 release
2023-04-08 20:01:07 +02:00
Simon Sessingø 847cb3e273 Merge pull request #655 from skipperbent/v5-development
Version 5.3.0.1
2023-04-08 20:00:55 +02:00
Simon Sessingø 2b4ae2b211 Merge pull request #654 from skipperbent/v5-exception-handling
PHP8: better exception handling
2023-04-08 19:56:55 +02:00
sessingo fa05d64a76 PHP8: better exception handling
Looks like PHP8 handles exceptions differently with Throwables used in cases where php-error occoured.
To fix this Throwable are now used to catch exception in routeRequest and any instance of Throwable will be converted to Exception.
2023-04-08 19:49:51 +02:00
Simon Sessingø 0ff9258776 Merge pull request #653 from skipperbent/v5-release
V5 release
2023-04-07 15:41:17 +02:00
Simon Sessingø b937b610de Merge pull request #651 from skipperbent/v5-development
Version 5.3.0.0
2023-04-07 15:41:04 +02:00
Simon Sessingø 5ac747374b Merge pull request #652 from skipperbent/fix-tostring
[BUGFIX] String return type
2023-04-07 15:38:41 +02:00
sessingo d6642a7f7b Changed behavior of router to always exspect returned output to be string. 2023-04-07 15:30:24 +02:00
Simon Sessingø ebf9224407 Merge pull request #649 from skipperbent/feature-csrfverifier
[!!!] CsrfVerifier changes
2023-04-07 14:36:13 +02:00
Simon Sessingø c635771fcd Merge pull request #650 from skipperbent/v5-fix-exception
[BUGFIX] Exception handling improvements
2023-04-07 14:35:56 +02:00
sessingo 791d69b24d Updated documentation 2023-04-07 13:08:40 +02:00
sessingo aa654a3ac6 [BUGFIX] Fixed exception-handler rewrite not always triggered 2023-04-07 13:05:27 +02:00
sessingo 6c6d81d3c9 [!!!] CsrfVerifier changes
- [!!!] Made $except and $include array not nullable.
- Added more customizable BaseCsrfVerifier. Can now be used as ticket for no hotlinking etc.
2023-04-06 13:09:26 +02:00
Simon Sessingø 5dc3e99d6e Merge pull request #648 from skipperbent/v5-release
V5 release
2023-04-02 03:24:30 +02:00
Simon Sessingø fadb783d3c Merge pull request #647 from skipperbent/v5-development
Fixed Response not initialized + incorrect phpDoc.
2023-04-02 03:24:14 +02:00
sessingo 8c79b74e14 Bugfixes 2023-04-02 03:22:38 +02:00
Simon Sessingø 578fa10fc9 Merge pull request #646 from skipperbent/v5-release
V5 release
2023-04-02 03:20:13 +02:00
Simon Sessingø 8477ea19d4 Merge pull request #645 from skipperbent/v5-development
Version 5.2.0.0
2023-04-02 03:20:01 +02:00
Simon Sessingø 77da37e00e Merge pull request #644 from skipperbent/v5-property-types
[!!!] Added type definitions to property types
2023-04-02 03:14:50 +02:00
sessingo 5946397c15 [!!!] Added type definitions to property types
- Request: optimized getIp method and reversed the order so proxy is always checked first.
2023-04-02 03:10:27 +02:00
Simon Sessingø 72ebada821 Merge pull request #643 from skipperbent/v5-release
V5 release
2023-03-30 16:27:25 +02:00
Simon Sessingø 4121011ef2 Merge pull request #642 from skipperbent/v5-development
Version 5.1.1.0
2023-03-30 16:26:44 +02:00
Simon Sessingø dc3b1fe74e Merge pull request #641 from skipperbent/fix-session
[BUGFIX] Fixed SESSION not shutdown correctly when using redirect.
2023-03-30 16:21:04 +02:00
sessingo c622ef97b0 [BUGFIX] Fixed SESSION not shutdown correctly when using redirect.
- Disabled multiple route rendering by default.
2023-03-30 16:15:06 +02:00
Simon Sessingø e5b5b0898f Merge pull request #640 from skipperbent/v5-release
V5 release
2023-03-25 03:21:27 +01:00
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ø 515fbc173c Merge pull request #633 from skipperbent/v5-release
V5 release
2023-02-13 14:06:39 +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ø 89b766ff2f Merge pull request #631 from skipperbent/v5-release
V5 release
2023-02-11 17:35:00 +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ø 0cb7fc416d Merge pull request #629 from skipperbent/v5-release
V5 release
2023-02-09 03:35: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
Simon Sessingø 1e9fa9c6a1 Merge pull request #627 from skipperbent/v4-development
Version 5.0.0.0
2023-02-09 03:07:54 +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
35 changed files with 539 additions and 326 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
+53 -30
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)
@@ -84,7 +85,7 @@ You can donate any amount of your choice by [clicking here](https://www.paypal.c
- [Registering new event](#registering-new-event)
- [Custom EventHandlers](#custom-eventhandlers)
- [Advanced](#advanced)
- [Disable multiple route rendering](#disable-multiple-route-rendering)
- [Multiple route rendering](#multiple-route-rendering)
- [Restrict access to IP](#restrict-access-to-ip)
- [Setting custom base path](#setting-custom-base-path)
- [Url rewriting](#url-rewriting)
@@ -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:
@@ -1013,6 +1036,17 @@ class CustomExceptionHandler implements IExceptionHandler
return;
}
/* Other error */
if($error instanceof MyCustomException) {
$request->setRewriteRoute(
// Add new route based on current url (minus query-string) and add custom parameters.
(new RouteUrl(url(null, null, []), 'PageController@error'))->setParameters(['exception' => $error])
);
return;
}
throw $error;
@@ -1469,11 +1503,12 @@ class DatabaseDebugHandler implements IEventHandler
# Advanced
## Disable multiple route rendering
## Multiple route rendering
By default the router will try to execute all routes that matches a given url. To stop the router from executing any further routes any method can return a value.
If you need multiple routes to be executed on the same url, you can enable this feature by setting `SimpleRouter::enableMultiRouteRendering(true)`
in your `routes.php` file.
This behavior can be easily disabled by setting `SimpleRouter::enableMultiRouteRendering(false)` in your `routes.php` file. This is the same behavior as version 3 and below.
This is most commonly used in advanced cases, for example in CMS systems where multiple routes needs to be rendered.
## Restrict access to IP
@@ -1707,6 +1742,7 @@ SimpleRouter::setCustomClassLoader(new MyCustomClassLoader());
php-di support was discontinued by version 4.3, however you can easily add it again by creating your own class-loader like the example below:
```php
use Pecee\SimpleRouter\ClassLoader\IClassLoader;
use Pecee\SimpleRouter\Exceptions\ClassNotFoundHttpException;
class MyCustomClassLoader implements IClassLoader
@@ -1727,19 +1763,14 @@ class MyCustomClassLoader implements IClassLoader
*
* @param string $class
* @return object
* @throws NotFoundHttpException
* @throws ClassNotFoundHttpException
*/
public function loadClass(string $class)
{
if (class_exists($class) === false) {
throw new NotFoundHttpException(sprintf('Class "%s" does not exist', $class), 404);
if ($this->container->has($class) === false) {
throw new ClassNotFoundHttpException($class, null, sprintf('Class "%s" does not exist', $class), 404, null);
}
try {
return $this->container->get($class);
} catch (\Exception $e) {
throw new NotFoundHttpException($e->getMessage(), (int)$e->getCode(), $e->getPrevious());
}
return $this->container->get($class);
}
/**
@@ -1747,15 +1778,11 @@ class MyCustomClassLoader implements IClassLoader
* @param object $class
* @param string $method
* @param array $parameters
* @return object
* @return string
*/
public function loadClassMethod($class, string $method, array $parameters)
{
try {
return $this->container->call([$class, $method], $parameters);
} catch (\Exception $e) {
throw new NotFoundHttpException($e->getMessage(), (int)$e->getCode(), $e->getPrevious());
}
return (string)$this->container->call([$class, $method], $parameters);
}
/**
@@ -1763,15 +1790,11 @@ class MyCustomClassLoader implements IClassLoader
*
* @param Callable $closure
* @param array $parameters
* @return mixed
* @return string
*/
public function loadClosure(callable $closure, array $parameters)
{
try {
return $this->container->call($closure, $parameters);
} catch (\Exception $e) {
throw new NotFoundHttpException($e->getMessage(), (int)$e->getCode(), $e->getPrevious());
}
return (string)$this->container->call($closure, $parameters);
}
}
```
+10 -5
View File
@@ -31,12 +31,12 @@
"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
}
}
}
+11 -11
View File
@@ -9,37 +9,37 @@ class InputFile implements IInputItem
/**
* @var string
*/
public $index;
public string $index;
/**
* @var string
*/
public $name;
public string $name;
/**
* @var string|null
*/
public $filename;
public ?string $filename = null;
/**
* @var int|null
*/
public $size;
public ?int $size = null;
/**
* @var int|null
* @var string|null
*/
public $type;
public ?string $type = null;
/**
* @var int
*/
public $errors;
public int $errors = 0;
/**
* @var string|null
*/
public $tmpName;
public ?string $tmpName = null;
public function __construct(string $index)
{
@@ -74,7 +74,7 @@ class InputFile implements IInputItem
'error' => null,
];
return (new static($values['index']))
return (new self($values['index']))
->setSize((int)$values['size'])
->setError((int)$values['error'])
->setType($values['type'])
@@ -104,9 +104,9 @@ class InputFile implements IInputItem
}
/**
* @return string
* @return int
*/
public function getSize(): string
public function getSize(): ?int
{
return $this->size;
}
+21 -17
View File
@@ -10,40 +10,40 @@ class InputHandler
/**
* @var array
*/
protected $get = [];
protected array $get = [];
/**
* @var array
*/
protected $post = [];
protected array $post = [];
/**
* @var array
*/
protected $file = [];
protected array $file = [];
/**
* @var Request
*/
protected $request;
protected Request $request;
/**
* Original post variables
* @var array
*/
protected $originalPost = [];
protected array $originalPost = [];
/**
* Original get/params variables
* @var array
*/
protected $originalParams = [];
protected array $originalParams = [];
/**
* Get original file variables
* @var array
*/
protected $originalFile = [];
protected array $originalFile = [];
/**
* Input constructor.
@@ -82,6 +82,10 @@ class InputHandler
if ($post !== false) {
$this->originalPost += $post;
}
} else {
$post = [];
parse_str($contents, $post);
$this->originalPost += $post;
}
}
@@ -108,7 +112,7 @@ class InputHandler
foreach ($files as $key => $value) {
// Parse multi dept file array
if(isset($value['name']) === false && is_array($value) === true) {
if (isset($value['name']) === false && is_array($value) === true) {
$list[$key] = $this->parseFiles($value, $key);
continue;
}
@@ -161,12 +165,12 @@ class InputHandler
try {
$file = InputFile::createFromArray([
'index' => ($key === '' && $originalIndex !== '') ? $originalIndex : $key,
'name' => $original['name'][$key],
'error' => $original['error'][$key],
'index' => ($key === '' && $originalIndex !== '') ? $originalIndex : $key,
'name' => $original['name'][$key],
'error' => $original['error'][$key],
'tmp_name' => $original['tmp_name'][$key],
'type' => $original['type'][$key],
'size' => $original['size'][$key],
'type' => $original['type'][$key],
'size' => $original['size'][$key],
]);
if (isset($output[$key]) === true) {
@@ -231,7 +235,7 @@ class InputHandler
{
$element = null;
if(count($methods) > 0) {
if (count($methods) > 0) {
$methods = is_array(...$methods) ? array_values(...$methods) : $methods;
}
@@ -303,9 +307,9 @@ class InputHandler
public function exists($index, ...$methods): bool
{
// Check array
if(is_array($index) === true) {
foreach($index as $key) {
if($this->value($key, null, ...$methods) === null) {
if (is_array($index) === true) {
foreach ($index as $key) {
if ($this->value($key, null, ...$methods) === null) {
return false;
}
}
+11 -2
View File
@@ -8,10 +8,18 @@ use IteratorAggregate;
class InputItem implements ArrayAccess, IInputItem, IteratorAggregate
{
public $index;
public $name;
public string $index;
public string $name;
/**
* @var mixed|null
*/
public $value;
/**
* @param string $index
* @param mixed $value
*/
public function __construct(string $index, $value = null)
{
$this->index = $index;
@@ -81,6 +89,7 @@ class InputItem implements ArrayAccess, IInputItem, IteratorAggregate
return isset($this->value[$offset]);
}
#[\ReturnTypeWillChange]
public function offsetGet($offset): ?self
{
if ($this->offsetExists($offset) === true) {
+25 -20
View File
@@ -17,18 +17,18 @@ class BaseCsrfVerifier implements IMiddleware
* For example: /admin/*
* @var array|null
*/
protected $except;
protected array $except = [];
/**
* Urls to include. Can be used to include urls from a certain path.
* @var array|null
*/
protected $include;
protected array $include = [];
/**
* @var ITokenProvider
*/
protected $tokenProvider;
protected ITokenProvider $tokenProvider;
/**
* BaseCsrfVerifier constructor.
@@ -38,6 +38,23 @@ class BaseCsrfVerifier implements IMiddleware
$this->tokenProvider = new CookieTokenProvider();
}
protected function isIncluded(Request $request): bool
{
if (count($this->include) > 0) {
foreach ($this->include as $includeUrl) {
$includeUrl = rtrim($includeUrl, '/');
if ($includeUrl[strlen($includeUrl) - 1] === '*') {
$includeUrl = rtrim($includeUrl, '*');
return $request->getUrl()->contains($includeUrl);
}
return ($includeUrl === rtrim($request->getUrl()->getRelativeUrl(false), '/'));
}
}
return false;
}
/**
* Check if the url matches the urls in the except property
* @param Request $request
@@ -45,11 +62,11 @@ class BaseCsrfVerifier implements IMiddleware
*/
protected function skip(Request $request): bool
{
if ($this->except === null || count($this->except) === 0) {
if (count($this->except) === 0) {
return false;
}
foreach($this->except as $url) {
foreach ($this->except as $url) {
$url = rtrim($url, '/');
if ($url[strlen($url) - 1] === '*') {
$url = rtrim($url, '*');
@@ -60,20 +77,9 @@ class BaseCsrfVerifier implements IMiddleware
if ($skip === true) {
if(is_array($this->include) === true && count($this->include) > 0) {
foreach($this->include as $includeUrl) {
$includeUrl = rtrim($includeUrl, '/');
if ($includeUrl[strlen($includeUrl) - 1] === '*') {
$includeUrl = rtrim($includeUrl, '*');
$skip = !$request->getUrl()->contains($includeUrl);
break;
}
$skip = !$this->isIncluded($request);
$skip = !($includeUrl === rtrim($request->getUrl()->getRelativeUrl(false), '/'));
}
}
if($skip === false) {
if ($skip === false) {
continue;
}
@@ -92,12 +98,11 @@ class BaseCsrfVerifier implements IMiddleware
*/
public function handle(Request $request): void
{
if ($this->skip($request) === false && $request->isPostBack() === true) {
if ($this->skip($request) === false && ($request->isPostBack() === true || $request->isPostBack() === true && $this->isIncluded($request) === true)) {
$token = $request->getInputHandler()->value(
static::POST_KEY,
$request->getHeader(static::HEADER_KEY),
Request::$requestTypesPost
);
if ($this->tokenProvider->validate((string)$token) === false) {
@@ -7,8 +7,8 @@ use Pecee\SimpleRouter\Exceptions\HttpException;
abstract class IpRestrictAccess implements IMiddleware
{
protected $ipBlacklist = [];
protected $ipWhitelist = [];
protected array $ipBlacklist = [];
protected array $ipWhitelist = [];
protected function validate(string $ip): bool
{
+36 -29
View File
@@ -29,7 +29,7 @@ class Request
* All request-types
* @var string[]
*/
public static $requestTypes = [
public static array $requestTypes = [
self::REQUEST_TYPE_GET,
self::REQUEST_TYPE_POST,
self::REQUEST_TYPE_PUT,
@@ -43,7 +43,7 @@ class Request
* Post request-types.
* @var string[]
*/
public static $requestTypesPost = [
public static array $requestTypesPost = [
self::REQUEST_TYPE_POST,
self::REQUEST_TYPE_PUT,
self::REQUEST_TYPE_PATCH,
@@ -55,65 +55,65 @@ class Request
*
* @var array
*/
private $data = [];
private array $data = [];
/**
* Server headers
* @var array
*/
protected $headers = [];
protected array $headers = [];
/**
* Request ContentType
* @var string
*/
protected $contentType;
protected string $contentType;
/**
* Request host
* @var string
* @var string|null
*/
protected $host;
protected ?string $host;
/**
* Current request url
* @var Url
*/
protected $url;
protected Url $url;
/**
* Request method
* @var string
*/
protected $method;
protected string $method;
/**
* Input handler
* @var InputHandler
*/
protected $inputHandler;
protected InputHandler $inputHandler;
/**
* Defines if request has pending rewrite
* @var bool
*/
protected $hasPendingRewrite = false;
protected bool $hasPendingRewrite = false;
/**
* @var ILoadableRoute|null
*/
protected $rewriteRoute;
protected ?ILoadableRoute $rewriteRoute = null;
/**
* Rewrite url
* @var string|null
*/
protected $rewriteUrl;
protected ?string $rewriteUrl = null;
/**
* @var array
*/
protected $loadedRoutes = [];
protected array $loadedRoutes = [];
/**
* Request constructor.
@@ -130,10 +130,10 @@ class Request
// Check if special IIS header exist, otherwise use default.
$url = $this->getHeader('unencoded-url');
if($url !== null){
if ($url !== null) {
$this->setUrl(new Url($url));
}else{
$this->setUrl(new Url(urldecode($this->getHeader('request-uri'))));
} 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')));
@@ -224,15 +224,17 @@ class Request
*/
public function getIp(bool $safeMode = false): ?string
{
$headers = ['remote-addr'];
if($safeMode === false) {
$headers = array_merge($headers, [
$headers = [];
if ($safeMode === false) {
$headers = [
'http-cf-connecting-ip',
'http-client-ip',
'http-x-forwarded-for',
]);
];
}
$headers[] = 'remote-addr';
return $this->getFirstHeader($headers);
}
@@ -301,9 +303,9 @@ class Request
*/
public function getFirstHeader(array $headers, $defaultValue = null)
{
foreach($headers as $header) {
foreach ($headers as $header) {
$header = $this->getHeader($header);
if($header !== null) {
if ($header !== null) {
return $header;
}
}
@@ -327,7 +329,7 @@ class Request
*/
protected function setContentType(string $contentType): self
{
if(strpos($contentType, ';') > 0) {
if (strpos($contentType, ';') > 0) {
$this->contentType = strtolower(substr($contentType, 0, strpos($contentType, ';')));
} else {
$this->contentType = strtolower($contentType);
@@ -364,12 +366,12 @@ 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');
}
/**
* Returns true when request-method is type that could contain data in the page body.
*
*
* @return bool
*/
public function isPostBack(): bool
@@ -393,11 +395,11 @@ class Request
{
$this->url = $url;
if ($this->url->getHost() === null) {
$this->url->setHost((string)$this->getHost());
if ($this->getHost() !== null) {
$url->setHost($this->getHost());
}
if($this->isSecure() === true) {
if ($this->isSecure() === true) {
$this->url->setScheme('https');
}
}
@@ -407,6 +409,11 @@ class Request
*/
public function setHost(?string $host): void
{
// Strip any potential ports from hostname
if (strpos((string)$host, ':') !== false) {
$host = strstr($host, strrchr($host, ':'), true);
}
$this->host = $host;
}
+6 -5
View File
@@ -7,7 +7,7 @@ use Pecee\Exceptions\InvalidArgumentException;
class Response
{
protected $request;
protected Request $request;
public function __construct(Request $request)
{
@@ -32,6 +32,8 @@ class Response
*
* @param string $url
* @param ?int $httpCode
*
* @return never
*/
public function redirect(string $url, ?int $httpCode = null): void
{
@@ -65,7 +67,6 @@ class Response
public function cache(string $eTag, int $lastModifiedTime = 2592000): self
{
$this->headers([
'Cache-Control: public',
sprintf('Last-Modified: %s GMT', gmdate('D, d M Y H:i:s', $lastModifiedTime)),
@@ -87,11 +88,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.');
@@ -128,4 +129,4 @@ class Response
return $this;
}
}
}
@@ -12,12 +12,12 @@ class CookieTokenProvider implements ITokenProvider
/**
* @var string
*/
protected $token;
protected ?string $token = null;
/**
* @var int
*/
protected $cookieTimeoutMinutes = 120;
protected int $cookieTimeoutMinutes = 120;
/**
* CookieTokenProvider constructor.
+46 -16
View File
@@ -10,47 +10,53 @@ class Url implements JsonSerializable
/**
* @var string|null
*/
private $originalUrl;
private ?string $originalUrl = null;
/**
* @var string|null
*/
private $scheme;
private ?string $scheme = null;
/**
* @var string|null
*/
private $host;
private ?string $host = null;
/**
* @var int|null
*/
private $port;
private ?int $port = null;
/**
* @var string|null
*/
private $username;
private ?string $username = null;
/**
* @var string|null
*/
private $password;
private ?string $password = null;
/**
* @var string|null
*/
private $path;
private ?string $path = null;
/**
* Original path with no sanitization to ending slash
* @var string|null
*/
private ?string $originalPath = null;
/**
* @var array
*/
private $params = [];
private array $params = [];
/**
* @var string|null
*/
private $fragment;
private ?string $fragment = null;
/**
* Url constructor.
@@ -61,18 +67,26 @@ class Url implements JsonSerializable
public function __construct(?string $url)
{
$this->originalUrl = $url;
$this->parse($url, true);
}
if ($url !== null && $url !== '/') {
public function parse(?string $url, bool $setOriginalPath = false): self
{
if ($url !== null) {
$data = $this->parseUrl($url);
$this->scheme = $data['scheme'] ?? null;
$this->host = $data['host'] ?? null;
$this->scheme = $data['scheme'] ?? $this->scheme;
$this->host = $data['host'] ?? $this->host;
$this->port = $data['port'] ?? null;
$this->username = $data['user'] ?? null;
$this->password = $data['pass'] ?? null;
if (isset($data['path']) === true) {
$this->setPath($data['path']);
if ($setOriginalPath === true) {
$this->originalPath = $data['path'];
}
}
$this->fragment = $data['fragment'] ?? null;
@@ -81,6 +95,8 @@ class Url implements JsonSerializable
$this->setQueryString($data['query']);
}
}
return $this;
}
/**
@@ -129,10 +145,15 @@ class Url implements JsonSerializable
/**
* Get url host
*
* @param bool $includeTrails Prepend // in front of hostname
* @return string|null
*/
public function getHost(): ?string
public function getHost(bool $includeTrails = false): ?string
{
if ((string)$this->host !== '' && $includeTrails === true) {
return '//' . $this->host;
}
return $this->host;
}
@@ -226,6 +247,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 +314,7 @@ class Url implements JsonSerializable
$params = [];
parse_str($queryString, $params);
if(count($params) > 0) {
if (count($params) > 0) {
return $this->setParams($params);
}
@@ -469,7 +499,7 @@ class Url implements JsonSerializable
{
$path = $this->path ?? '/';
if($includeParams === false) {
if ($includeParams === false) {
return $path;
}
@@ -506,7 +536,7 @@ class Url implements JsonSerializable
*/
public function jsonSerialize(): string
{
return $this->getRelativeUrl();
return $this->getHost(true) . $this->getRelativeUrl();
}
public function __toString(): string
@@ -27,11 +27,11 @@ class ClassLoader implements IClassLoader
* @param object $class
* @param string $method
* @param array $parameters
* @return object
* @return string
*/
public function loadClassMethod($class, string $method, array $parameters)
public function loadClassMethod($class, string $method, array $parameters): string
{
return call_user_func_array([$class, $method], array_values($parameters));
return (string)call_user_func_array([$class, $method], array_values($parameters));
}
/**
@@ -39,11 +39,11 @@ class ClassLoader implements IClassLoader
*
* @param Callable $closure
* @param array $parameters
* @return mixed
* @return string
*/
public function loadClosure(Callable $closure, array $parameters)
public function loadClosure(callable $closure, array $parameters): string
{
return call_user_func_array($closure, array_values($parameters));
return (string)call_user_func_array($closure, array_values($parameters));
}
}
@@ -17,7 +17,7 @@ interface IClassLoader
* @param object $class
* @param string $method
* @param array $parameters
* @return object
* @return mixed
*/
public function loadClassMethod($class, string $method, array $parameters);
@@ -12,17 +12,17 @@ class EventArgument implements IEventArgument
* Event name
* @var string
*/
protected $eventName;
protected string $eventName;
/**
* @var Router
*/
protected $router;
protected Router $router;
/**
* @var array
*/
protected $arguments = [];
protected array $arguments = [];
public function __construct(string $eventName, Router $router, array $arguments = [])
{
@@ -9,12 +9,12 @@ class ClassNotFoundHttpException extends NotFoundHttpException
/**
* @var string
*/
protected $class;
protected string $class;
/**
* @var string|null
*/
protected $method;
protected ?string $method = null;
public function __construct(string $class, ?string $method = null, string $message = "", int $code = 0, Throwable $previous = null)
{
@@ -20,7 +20,7 @@ class CallbackExceptionHandler implements IExceptionHandler
/**
* @var Closure
*/
protected $callback;
protected Closure $callback;
public function __construct(Closure $callback)
{
@@ -13,7 +13,7 @@ class DebugEventHandler implements IEventHandler
* Debug callback
* @var Closure
*/
protected $callback;
protected Closure $callback;
public function __construct()
{
@@ -97,7 +97,7 @@ class EventHandler implements IEventHandler
* All available events
* @var array
*/
public static $events = [
public static array $events = [
self::EVENT_ALL,
self::EVENT_INIT,
self::EVENT_LOAD,
@@ -120,7 +120,7 @@ class EventHandler implements IEventHandler
* List of all registered events
* @var array
*/
private $registeredEvents = [];
private array $registeredEvents = [];
/**
* Register new event
+19 -13
View File
@@ -6,23 +6,24 @@ use Pecee\Http\Middleware\IMiddleware;
use Pecee\Http\Request;
use Pecee\SimpleRouter\Exceptions\HttpException;
use Pecee\SimpleRouter\Router;
use Pecee\SimpleRouter\SimpleRouter;
abstract class LoadableRoute extends Route implements ILoadableRoute
{
/**
* @var string
*/
protected $url;
protected string $url;
/**
* @var string
*/
protected $name;
protected ?string $name = null;
/**
* @var string|null
*/
protected $regex;
protected ?string $regex = null;
/**
* Loads and renders middlewares-classes
@@ -82,15 +83,18 @@ abstract class LoadableRoute extends Route implements ILoadableRoute
{
$this->url = ($url === '/') ? '/' : '/' . trim($url, '/') . '/';
$parameters = [];
if (strpos($this->url, $this->paramModifiers[0]) !== false) {
$regex = sprintf(static::PARAMETERS_REGEX_FORMAT, $this->paramModifiers[0], $this->paramOptionalSymbol, $this->paramModifiers[1]);
if ((bool)preg_match_all('/' . $regex . '/u', $this->url, $matches) !== false) {
$this->parameters = array_fill_keys($matches[1], null);
$parameters = array_fill_keys($matches[1], null);
}
}
$this->parameters = $parameters;
return $this;
}
@@ -135,12 +139,6 @@ abstract class LoadableRoute extends Route implements ILoadableRoute
{
$url = $this->getUrl();
$group = $this->getGroup();
if ($group !== null && count($group->getDomains()) !== 0) {
$url = '//' . $group->getDomains()[0] . $url;
}
/* Create the param string - {parameter} */
$param1 = $this->paramModifiers[0] . '%s' . $this->paramModifiers[1];
@@ -167,14 +165,22 @@ 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 . '/';
}
}
return rtrim('/' . ltrim($url, '/'), '/') . '/';
$url = rtrim('/' . ltrim($url, '/'), '/') . '/';
$group = $this->getGroup();
if ($group !== null && count($group->getDomains()) !== 0 && SimpleRouter::request()->getHost() !== $group->getDomains()[0]) {
$url = '//' . $group->getDomains()[0] . $url;
}
return $url;
}
/**
@@ -195,7 +201,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($name);
}
/**
+55 -20
View File
@@ -18,31 +18,37 @@ abstract class Route implements IRoute
*
* @var bool
*/
protected $filterEmptyParams = true;
protected bool $filterEmptyParams = true;
/**
* If true the last parameter of the route will include ending trail/slash.
* @var bool
*/
protected bool $slashParameterEnabled = false;
/**
* Default regular expression used for parsing parameters.
* @var string|null
*/
protected $defaultParameterRegex;
protected $paramModifiers = '{}';
protected $paramOptionalSymbol = '?';
protected $urlRegex = '/^%s\/?$/u';
protected $group;
protected $parent;
protected ?string $defaultParameterRegex = null;
protected string $paramModifiers = '{}';
protected string $paramOptionalSymbol = '?';
protected string $urlRegex = '/^%s\/?$/u';
protected ?IGroupRoute $group = null;
protected ?IRoute $parent = null;
/**
* @var string|callable|null
*/
protected $callback;
protected $defaultNamespace;
protected ?string $defaultNamespace = null;
/* Default options */
protected $namespace;
protected $requestMethods = [];
protected $where = [];
protected $parameters = [];
protected $originalParameters = [];
protected $middlewares = [];
protected ?string $namespace = null;
protected array $requestMethods = [];
protected array $where = [];
protected array $parameters = [];
protected array $originalParameters = [];
protected array $middlewares = [];
/**
* Render route
@@ -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,8 +129,10 @@ abstract class Route implements IRoute
);
// 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 = [];
@@ -132,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 = '';
@@ -154,6 +162,7 @@ abstract class Route implements IRoute
}
}
// Get name of last param
if (trim($urlRegex) === '' || (bool)preg_match(sprintf($this->urlRegex, $urlRegex), $url, $matches) === false) {
return null;
}
@@ -167,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) {
@@ -175,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;
@@ -387,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.
*
@@ -416,6 +443,10 @@ abstract class Route implements IRoute
$values['defaultParameterRegex'] = $this->defaultParameterRegex;
}
if ($this->slashParameterEnabled === true) {
$values['includeSlash'] = $this->slashParameterEnabled;
}
return $values;
}
@@ -453,6 +484,10 @@ abstract class Route implements IRoute
$this->setDefaultParameterRegex($settings['defaultParameterRegex']);
}
if (isset($settings['includeSlash']) === true) {
$this->setSlashParameterEnabled($settings['includeSlash']);
}
return $this;
}
@@ -3,13 +3,14 @@
namespace Pecee\SimpleRouter\Route;
use Pecee\Http\Request;
use Pecee\SimpleRouter\SimpleRouter;
class RouteController extends LoadableRoute implements IControllerRoute
{
protected $defaultMethod = 'index';
protected $controller;
protected $method;
protected $names = [];
protected string $defaultMethod = 'index';
protected string $controller;
protected ?string $method = null;
protected array $names = [];
public function __construct($url, $controller)
{
@@ -77,13 +78,15 @@ class RouteController extends LoadableRoute implements IControllerRoute
$group = $this->getGroup();
if ($group !== null && count($group->getDomains()) !== 0) {
$url .= '//' . $group->getDomains()[0];
$url .= '/' . trim($this->getUrl(), '/') . '/' . strtolower((string)$method) . implode('/', $parameters);
$url = '/' . trim($url, '/') . '/';
if ($group !== null && count($group->getDomains()) !== 0 && SimpleRouter::request()->getHost() !== $group->getDomains()[0]) {
$url = '//' . $group->getDomains()[0] . $url;
}
$url .= '/' . trim($this->getUrl(), '/') . '/' . strtolower($method) . implode('/', $parameters);
return '/' . trim($url, '/') . '/';
return $url;
}
public function matchRoute(string $url, Request $request): bool
+11 -11
View File
@@ -7,12 +7,12 @@ use Pecee\SimpleRouter\Handlers\IExceptionHandler;
class RouteGroup extends Route implements IGroupRoute
{
protected $urlRegex = '/^%s\/?/u';
protected $prefix;
protected $name;
protected $domains = [];
protected $exceptionHandlers = [];
protected $mergeExceptionHandlers = true;
protected string $urlRegex = '/^%s\/?/u';
protected ?string $prefix = null;
protected ?string $name = null;
protected array $domains = [];
protected array $exceptionHandlers = [];
protected bool $mergeExceptionHandlers = true;
/**
* Method called to check if a domain matches
@@ -22,7 +22,7 @@ class RouteGroup extends Route implements IGroupRoute
*/
public function matchDomain(Request $request): bool
{
if ($this->domains === null || count($this->domains) === 0) {
if (count($this->domains) === 0) {
return true;
}
@@ -33,7 +33,7 @@ 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;
@@ -60,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) {
@@ -74,7 +74,7 @@ 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 */
@@ -220,7 +220,7 @@ class RouteGroup extends Route implements IGroupRoute
$this->setExceptionHandlers((array)$settings['exceptionHandler']);
}
if ($merge === false && isset($settings['domain']) === true) {
if (isset($settings['domain']) === true) {
$this->setDomains((array)$settings['domain']);
}
+42 -27
View File
@@ -3,31 +3,32 @@
namespace Pecee\SimpleRouter\Route;
use Pecee\Http\Request;
use Pecee\SimpleRouter\SimpleRouter;
class RouteResource extends LoadableRoute implements IControllerRoute
{
protected $urls = [
'index' => '',
'create' => 'create',
'store' => '',
'show' => '',
'edit' => 'edit',
'update' => '',
protected array $urls = [
'index' => '',
'create' => 'create',
'store' => '',
'show' => '',
'edit' => 'edit',
'update' => '',
'destroy' => '',
];
protected $methodNames = [
'index' => 'index',
'create' => 'create',
'store' => 'store',
'show' => 'show',
'edit' => 'edit',
'update' => 'update',
protected array $methodNames = [
'index' => 'index',
'create' => 'create',
'store' => 'store',
'show' => 'show',
'edit' => 'edit',
'update' => 'update',
'destroy' => 'destroy',
];
protected $names = [];
protected $controller;
protected array $names = [];
protected string $controller;
public function __construct($url, $controller)
{
@@ -69,11 +70,25 @@ class RouteResource extends LoadableRoute implements IControllerRoute
public function findUrl(?string $method = null, $parameters = null, ?string $name = null): string
{
$url = array_search($name, $this->names, true);
if ($url !== false) {
return rtrim($this->url . $this->urls[$url], '/') . '/';
$parametersUrl = '';
if ($parameters !== null && count($parameters) > 0) {
$parametersUrl = join('/', $parameters) . '/';
}
return $this->url;
if ($url !== false) {
return rtrim($this->url . $parametersUrl . $this->urls[$url], '/') . '/';
}
$url = $this->url . $parametersUrl;
$group = $this->getGroup();
if ($group !== null && count($group->getDomains()) !== 0 && SimpleRouter::request()->getHost() !== $group->getDomains()[0]) {
$url = '//' . $group->getDomains()[0] . $url;
}
return $url;
}
protected function call($method): bool
@@ -99,14 +114,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
@@ -172,12 +187,12 @@ class RouteResource extends LoadableRoute implements IControllerRoute
$this->name = $name;
$this->names = [
'index' => $this->name . '.index',
'create' => $this->name . '.create',
'store' => $this->name . '.store',
'show' => $this->name . '.show',
'edit' => $this->name . '.edit',
'update' => $this->name . '.update',
'index' => $this->name . '.index',
'create' => $this->name . '.create',
'store' => $this->name . '.store',
'show' => $this->name . '.show',
'edit' => $this->name . '.edit',
'update' => $this->name . '.update',
'destroy' => $this->name . '.destroy',
];
+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) {
+47 -58
View File
@@ -28,56 +28,56 @@ class Router
* Current request
* @var Request
*/
protected $request;
protected Request $request;
/**
* Defines if a route is currently being processed.
* @var bool
*/
protected $isProcessingRoute;
protected bool $isProcessingRoute;
/**
* Defines all data from current processing route.
* @var ILoadableRoute
*/
protected $currentProcessingRoute;
protected ILoadableRoute $currentProcessingRoute;
/**
* All added routes
* @var array
*/
protected $routes = [];
protected array $routes = [];
/**
* List of processed routes
* @var array|ILoadableRoute[]
*/
protected $processedRoutes = [];
protected array $processedRoutes = [];
/**
* Stack of routes used to keep track of sub-routes added
* when a route is being processed.
* @var array
*/
protected $routeStack = [];
protected array $routeStack = [];
/**
* List of added bootmanagers
* @var array
*/
protected $bootManagers = [];
protected array $bootManagers = [];
/**
* Csrf verifier class
* @var BaseCsrfVerifier|null
*/
protected $csrfVerifier;
protected ?BaseCsrfVerifier $csrfVerifier;
/**
* Get exception handlers
* @var array
*/
protected $exceptionHandlers = [];
protected array $exceptionHandlers = [];
/**
* List of loaded exception that has been loaded.
@@ -85,44 +85,44 @@ class Router
*
* @var array
*/
protected $loadedExceptionHandlers = [];
protected array $loadedExceptionHandlers = [];
/**
* Enable or disabled debugging
* @var bool
*/
protected $debugEnabled = false;
protected bool $debugEnabled = false;
/**
* The start time used when debugging is enabled
* @var float
*/
protected $debugStartTime;
protected float $debugStartTime;
/**
* List containing all debug messages
* @var array
*/
protected $debugList = [];
protected array $debugList = [];
/**
* Contains any registered event-handler.
* @var array
*/
protected $eventHandlers = [];
protected array $eventHandlers = [];
/**
* Class loader instance
* @var IClassLoader
*/
protected $classLoader;
protected IClassLoader $classLoader;
/**
* When enabled the router will render all routes that matches.
* When disabled the router will stop execution when first route is found.
* @var bool
*/
protected $renderMultipleRoutes = true;
protected bool $renderMultipleRoutes = false;
/**
* Router constructor.
@@ -166,7 +166,7 @@ class Router
public function addRoute(IRoute $route): IRoute
{
$this->fireEvents(EventHandler::EVENT_ADD_ROUTE, [
'route' => $route,
'route' => $route,
'isSubRoute' => $this->isProcessingRoute,
]);
@@ -307,7 +307,7 @@ class Router
$this->debug('Rendering bootmanager "%s"', $className);
$this->fireEvents(EventHandler::EVENT_RENDER_BOOTMANAGER, [
'bootmanagers' => $this->bootManagers,
'bootmanager' => $manager,
'bootmanager' => $manager,
]);
/* Render bootmanager */
@@ -342,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) {
return $this->handleException($e);
}
}
$output = $this->routeRequest();
@@ -377,7 +381,7 @@ 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;
@@ -401,7 +405,7 @@ class Router
}
$this->fireEvents(EventHandler::EVENT_RENDER_MIDDLEWARES, [
'route' => $route,
'route' => $route,
'middlewares' => $route->getMiddlewares(),
]);
@@ -423,7 +427,7 @@ class Router
$routeOutput = $route->renderRoute($this->request, $this);
if ($this->renderMultipleRoutes === true) {
if ($routeOutput !== null) {
if ($routeOutput !== '') {
return $routeOutput;
}
@@ -440,12 +444,12 @@ class Router
}
} catch (Exception $e) {
$this->handleException($e);
return $this->handleException($e);
}
if ($methodNotAllowed === true) {
$message = sprintf('Route "%s" or method "%s" not allowed.', $this->request->getUrl()->getPath(), $this->request->getMethod());
$this->handleException(new NotFoundHttpException($message, 403));
return $this->handleException(new NotFoundHttpException($message, 403));
}
if (count($this->request->getLoadedRoutes()) === 0) {
@@ -496,7 +500,7 @@ class Router
$this->request->setHasPendingRewrite(false);
$this->fireEvents(EventHandler::EVENT_REWRITE, [
'rewriteUrl' => $this->request->getRewriteUrl(),
'rewriteUrl' => $this->request->getRewriteUrl(),
'rewriteRoute' => $this->request->getRewriteRoute(),
]);
@@ -517,7 +521,7 @@ class Router
$this->debug('Starting exception handling for "%s"', get_class($e));
$this->fireEvents(EventHandler::EVENT_LOAD_EXCEPTIONS, [
'exception' => $e,
'exception' => $e,
'exceptionHandlers' => $this->exceptionHandlers,
]);
@@ -529,8 +533,8 @@ class Router
}
$this->fireEvents(EventHandler::EVENT_RENDER_EXCEPTION, [
'exception' => $e,
'exceptionHandler' => $handler,
'exception' => $e,
'exceptionHandler' => $handler,
'exceptionHandlers' => $this->exceptionHandlers,
]);
@@ -552,7 +556,7 @@ class Router
$this->debug('Exception handler contains rewrite, reloading routes');
$this->fireEvents(EventHandler::EVENT_REWRITE, [
'rewriteUrl' => $this->request->getRewriteUrl(),
'rewriteUrl' => $this->request->getRewriteUrl(),
'rewriteRoute' => $this->request->getRewriteRoute(),
]);
@@ -608,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;
@@ -663,9 +667,9 @@ class Router
$this->debug('Finding url', func_get_args());
$this->fireEvents(EventHandler::EVENT_GET_URL, [
'name' => $name,
'name' => $name,
'parameters' => $parameters,
'getParams' => $getParams,
'getParams' => $getParams,
]);
if ($name === '' && $parameters === '') {
@@ -686,10 +690,7 @@ class Router
/* If nothing is defined and a route is loaded we use that */
if ($name === null && $loadedRoute !== null) {
return $this->request
->getUrlCopy()
->setPath($loadedRoute->findUrl($loadedRoute->getMethod(), $parameters, $name))
->setParams($getParams);
return $this->request->getUrlCopy()->parse($loadedRoute->findUrl($loadedRoute->getMethod(), $parameters, $name))->setParams($getParams);
}
if ($name !== null) {
@@ -697,10 +698,7 @@ class Router
$route = $this->findRoute($name);
if ($route !== null) {
return $this->request
->getUrlCopy()
->setPath($route->findUrl($route->getMethod(), $parameters, $name))
->setParams($getParams);
return $this->request->getUrlCopy()->parse($route->findUrl($route->getMethod(), $parameters, $name))->setParams($getParams);
}
}
@@ -715,18 +713,12 @@ class Router
/* Check if the route contains the name/alias */
if ($processedRoute->hasName($controller) === true) {
return $this->request
->getUrlCopy()
->setPath($processedRoute->findUrl($method, $parameters, $name))
->setParams($getParams);
return $this->request->getUrlCopy()->parse($processedRoute->findUrl($method, $parameters, $name))->setParams($getParams);
}
/* Check if the route controller is equal to the name */
if ($processedRoute instanceof IControllerRoute && strtolower($processedRoute->getController()) === strtolower($controller)) {
return $this->request
->getUrlCopy()
->setPath($processedRoute->findUrl($method, $parameters, $name))
->setParams($getParams);
return $this->request->getUrlCopy()->parse($processedRoute->findUrl($method, $parameters, $name))->setParams($getParams);
}
}
@@ -736,10 +728,7 @@ class Router
$url = trim(implode('/', array_merge((array)$name, (array)$parameters)), '/');
$url = (($url === '') ? '/' : '/' . $url . '/');
return $this->request
->getUrlCopy()
->setPath($url)
->setParams($getParams);
return $this->request->getUrlCopy()->parse($url)->setParams($getParams);
}
/**
@@ -909,8 +898,8 @@ class Router
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
$this->debugList[] = [
'message' => vsprintf($message, $args),
'time' => number_format(microtime(true) - $this->debugStartTime, 10),
'trace' => end($trace),
'time' => number_format(microtime(true) - $this->debugStartTime, 10),
'trace' => end($trace),
];
}
@@ -936,7 +925,7 @@ class Router
{
return $this->debugList;
}
/**
* Get the current processing route details.
*
@@ -969,4 +958,4 @@ class Router
return $this;
}
}
}
+5 -5
View File
@@ -37,19 +37,19 @@ class SimpleRouter
* Default namespace added to all routes
* @var string|null
*/
protected static $defaultNamespace;
protected static ?string $defaultNamespace = null;
/**
* The response object
* @var Response
* @var Response|null
*/
protected static $response;
protected static ?Response $response = null;
/**
* Router instance
* @var Router
*/
protected static $router;
protected static ?Router $router = null;
/**
* Start routing
@@ -493,7 +493,7 @@ class SimpleRouter
* Prepends the default namespace to all new routes added.
*
* @param ILoadableRoute|IRoute $route
* @return IRoute
* @return IRoute|ILoadableRoute
*/
public static function addDefaultNamespace(IRoute $route): IRoute
{
@@ -2,12 +2,12 @@
class DummyCsrfVerifier extends \Pecee\Http\Middleware\BaseCsrfVerifier {
protected $except = [
protected array $except = [
'/exclude-page',
'/exclude-all/*',
];
protected $include = [
protected array $include = [
'/exclude-all/include-page',
];
@@ -2,12 +2,12 @@
class IpRestrictMiddleware extends \Pecee\Http\Middleware\IpRestrictAccess {
protected $ipBlacklist = [
protected array $ipBlacklist = [
'5.5.5.5',
'8.8.*',
];
protected $ipWhitelist = [
protected array $ipWhitelist = [
'8.8.2.2',
];
@@ -0,0 +1,11 @@
<?php
use Pecee\Http\Request;
class DummyLoadableRoute extends Pecee\SimpleRouter\Route\LoadableRoute {
public function matchRoute(string $url, Request $request): bool
{
return false;
}
}
@@ -0,0 +1,27 @@
<?php
require_once 'Dummy/Route/DummyLoadableRoute.php';
class LoadableRouteTest extends \PHPUnit\Framework\TestCase
{
public function testSetUrlUpdatesParameters()
{
$route = new DummyLoadableRoute();
$this->assertEmpty($route->getParameters());
$route->setUrl('/');
$this->assertEmpty($route->getParameters());
$expected = ['param' => null, 'optionalParam' => null];
$route->setUrl('/{param}/{optionalParam?}');
$this->assertEquals($expected, $route->getParameters());
$expected = ['otherParam' => null];
$route->setUrl('/{otherParam}');
$this->assertEquals($expected, $route->getParameters());
$expected = [];
$route->setUrl('/');
$this->assertEquals($expected, $route->getParameters());
}
}
@@ -63,7 +63,22 @@ class RouterResourceTest extends \PHPUnit\Framework\TestCase
$response = TestRouter::debugOutput('/resource/38', 'get');
$this->assertEquals('show 38', $response);
}
public function testResourceUrls()
{
TestRouter::resource('/resource', 'ResourceController')->name('resource');
TestRouter::debugNoReset('/resource');
$this->assertEquals('/resource/3/create/', TestRouter::router()->getUrl('resource.create', ['id' => 3]));
$this->assertEquals('/resource/5/edit/', TestRouter::router()->getUrl('resource.edit', ['id' => 5]));
$this->assertEquals('/resource/6/', TestRouter::router()->getUrl('resource.update', ['id' => 6]));
$this->assertEquals('/resource/9/', TestRouter::router()->getUrl('resource.destroy', ['id' => 9]));
$this->assertEquals('/resource/12/', TestRouter::router()->getUrl('resource.delete', ['id' => 12]));
$this->assertEquals('/resource/', TestRouter::router()->getUrl('resource'));
TestRouter::router()->reset();
}
}
+41 -19
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
@@ -77,11 +94,13 @@ class RouterUrlTest extends \PHPUnit\Framework\TestCase
public function testSimilarUrls()
{
TestRouter::reset();
// Match normal route on alias
TestRouter::get('/url11', 'DummyController@method1');
TestRouter::get('/url22', 'DummyController@method2');
TestRouter::get('/url33', 'DummyController@method2')->name('match');
TestRouter::debugNoReset('/url33', 'get');
$this->assertEquals(TestRouter::getUrl('match'), TestRouter::getUrl());
@@ -167,7 +186,7 @@ class RouterUrlTest extends \PHPUnit\Framework\TestCase
// Should match /?jackdaniels=true&cola=yeah
$this->assertEquals('/?jackdaniels=true&cola=yeah', TestRouter::getUrl('home', null, ['jackdaniels' => 'true', 'cola' => 'yeah']));
TestRouter::router()->reset();
TestRouter::reset();
}
@@ -191,7 +210,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 +253,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 +264,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 +306,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 +327,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 +344,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 +364,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;
});
});
+6 -1
View File
@@ -8,11 +8,16 @@ class TestRouter extends \Pecee\SimpleRouter\SimpleRouter
static::request()->setHost('testhost.com');
}
public static function reset(): void
{
static::$router = null;
}
public static function debugNoReset(string $testUrl, string $testMethod = 'get'): void
{
$request = static::request();
$request->setUrl((new \Pecee\Http\Url($testUrl))->setHost('local.unitTest'));
$request->setUrl((new \Pecee\Http\Url($testUrl)));
$request->setMethod($testMethod);
static::start();