Compare commits

...

86 Commits

Author SHA1 Message Date
sessingo f3c6015a59 Fixed Resource-type not respecting parameters when using getUrl (issue: #666) 2023-05-06 17:29:33 +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
sessingo 4778a8f29e InputItem: php8.1 deprecated warning-Added returnTypeWillChange to offsetGet 2023-04-21 11:28:19 +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ø 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
sessingo 8c79b74e14 Bugfixes 2023-04-02 03:22:38 +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ø 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ø 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
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
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
sessingo e34fe47a04 Fixed offsetGet return type deprication warning 2023-02-09 03:29:41 +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ø 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ø 4b8dbdc9e5 Merge pull request #575 from skipperbent/v4-development
Updated documentation
2021-07-16 22:31:06 +02:00
Simon Sessingø 7fe66ac938 Updated documentation
- Changed Router references to SimpleRouter.
- Updated ErrorHandler example.
- Minor tweaks
2021-07-16 22:28:54 +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ø e5552a88cf Merge pull request #568 from skipperbent/v4-development
Version 4.3.6.1
2021-06-15 10:16:25 +02:00
Simon Sessingø 7d80517c2f Merge pull request #567 from skipperbent/v4-setmatch-parameters
Fixed custom regex (setMatch) not setting parsed parameters (issue: #566)
2021-06-15 10:13:54 +02:00
Simon Sessingø b3c135c723 Development
- Fixed DebugHandler::fireEvent not providing correct arguments when calling fireEvents.
- Fixed custom regex setMatch not setting parsed parameters correctly (issue: #566).
- Added unit-tests for catching issue in the future.
- Added php-stan typehints.
2021-06-15 10:11:09 +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ø e990b95c50 Merge pull request #563 from skipperbent/v4-development
Version 4.3.6.0
2021-06-09 09:19:14 +02:00
Simon Sessingø a35400b7a0 Merge pull request #562 from skipperbent/v4-cleanup-bugfixes
Development
2021-06-09 09:16:39 +02:00
Simon Sessingø 22606dfc12 Updates 2021-06-09 09:13:11 +02:00
Simon Sessingø 5cd6cab801 Development
- Fixed issue causing default-namespace to add duplicate namespace when using type-hints (issue: #561).
- Fixed phpstan issues.
- Tests: Fixed TestRouter not resetting namespace upon reset.
- Tests: Added NSController (namespace controller) class.
- Tests: added test for class hint + default namespace case.
- Composer: added phpstan support + configuration.
- Composer: updated phpunit version.
2021-06-09 08:54:24 +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ø 319ce7a569 Merge pull request #559 from skipperbent/v4-development
Version 4.3.5.0
2021-05-20 15:19:00 +02:00
Simon Sessingø 4dff4006bf Merge pull request #558 from skipperbent/v4-exists-array
Added support for InputHandler::exists to check array of indexes.
2021-05-20 15:15:51 +02:00
Simon Sessingø eea30d0f59 Simplified RouteController and RouteResource by moving common group-match code to parent method. 2021-05-20 15:15:26 +02:00
Simon Sessingø ece9d30905 Added support for InputHandler::exists to check array of indexes.
- Updated documentation to reflect changes.
2021-05-20 15:03:56 +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ø d4de7fc3df Merge pull request #556 from skipperbent/v4-development
Version 4.3.4.2
2021-05-19 22:05:44 +02:00
Simon Sessingø 03ef9dfb74 Merge pull request #555 from skipperbent/v4-error-group-fix
Fixed issue with SimpleRouter::error not firing within group (issue: #551)
2021-05-19 22:03:39 +02:00
Simon Sessingø 4c5f825c97 Fixed issue with SimpleRouter::error not firing within group (issue: #551).
- Fixed variable incorrect variable reference in `InputItem` class.
- Added new `Router::addExceptionHandler` method.
- Added parameter types in `Url` class.
- Fixed phpdoc parameter-type for `Request::getHeader`.
2021-05-19 22:00:42 +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
40 changed files with 940 additions and 339 deletions
+2 -2
View File
@@ -17,10 +17,10 @@ jobs:
- ubuntu-latest - ubuntu-latest
- windows-latest - windows-latest
php-version: php-version:
- 7.1
- 7.4 - 7.4
- 8.0
phpunit-version: phpunit-version:
- 7.5.20 - 8.5.32
dependencies: dependencies:
- lowest - lowest
- highest - highest
+2 -1
View File
@@ -1,4 +1,5 @@
composer.lock composer.lock
vendor/ vendor/
.idea/ .idea/
.phpunit.result.cache .phpunit.result.cache
tests/tmp
+134 -21
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) - [Available methods](#available-methods)
- [Multiple HTTP-verbs](#multiple-http-verbs) - [Multiple HTTP-verbs](#multiple-http-verbs)
- [Route parameters](#route-parameters) - [Route parameters](#route-parameters)
- [Required parameters](#required-parameters) - [Required parameters](#required-parameters)
- [Optional parameters](#optional-parameters) - [Optional parameters](#optional-parameters)
- [Regular expression constraints](#regular-expression-constraints) - [Including slash in parameters](#including-slash-in-parameters)
- [Regular expression route-match](#regular-expression-route-match) - [Regular expression constraints](#regular-expression-constraints)
- [Custom regex for matching parameters](#custom-regex-for-matching-parameters) - [Regular expression route-match](#regular-expression-route-match)
- [Custom regex for matching parameters](#custom-regex-for-matching-parameters)
- [Named routes](#named-routes) - [Named routes](#named-routes)
- [Generating URLs To Named Routes](#generating-urls-to-named-routes) - [Generating URLs To Named Routes](#generating-urls-to-named-routes)
- [Router groups](#router-groups) - [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) - [ExceptionHandlers](#exceptionhandlers)
- [Handling 404, 403 and other errors](#handling-404-403-and-other-errors) - [Handling 404, 403 and other errors](#handling-404-403-and-other-errors)
- [Using custom exception handlers](#using-custom-exception-handlers) - [Using custom exception handlers](#using-custom-exception-handlers)
- [Prevent merge of parent exception-handlers](#prevent-merge-of-parent-exception-handlers)
- [Urls](#urls) - [Urls](#urls)
- [Get the current url](#get-the-current-url) - [Get the current url](#get-the-current-url)
- [Get by name (single route)](#get-by-name-single-route) - [Get by name (single route)](#get-by-name-single-route)
@@ -77,12 +79,13 @@ You can donate any amount of your choice by [clicking here](https://www.paypal.c
- [Get parameter object](#get-parameter-object) - [Get parameter object](#get-parameter-object)
- [Managing files](#managing-files) - [Managing files](#managing-files)
- [Get all parameters](#get-all-parameters) - [Get all parameters](#get-all-parameters)
- [Check if parameters exists](#check-if-parameters-exists)
- [Events](#events) - [Events](#events)
- [Available events](#available-events) - [Available events](#available-events)
- [Registering new event](#registering-new-event) - [Registering new event](#registering-new-event)
- [Custom EventHandlers](#custom-eventhandlers) - [Custom EventHandlers](#custom-eventhandlers)
- [Advanced](#advanced) - [Advanced](#advanced)
- [Disable multiple route rendering](#disable-multiple-route-rendering) - [Multiple route rendering](#multiple-route-rendering)
- [Restrict access to IP](#restrict-access-to-ip) - [Restrict access to IP](#restrict-access-to-ip)
- [Setting custom base path](#setting-custom-base-path) - [Setting custom base path](#setting-custom-base-path)
- [Url rewriting](#url-rewriting) - [Url rewriting](#url-rewriting)
@@ -488,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 ### 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: 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:
@@ -790,7 +815,7 @@ If you want to store the token elsewhere, please refer to the "Creating custom T
When you've created your CSRF-verifier you need to tell simple-php-router that it should use it. You can do this by adding the following line in your `routes.php` file: When you've created your CSRF-verifier you need to tell simple-php-router that it should use it. You can do this by adding the following line in your `routes.php` file:
```php ```php
Router::csrfVerifier(new \Demo\Middlewares\CsrfVerifier()); SimpleRouter::csrfVerifier(new \Demo\Middlewares\CsrfVerifier());
``` ```
## Getting CSRF-token ## Getting CSRF-token
@@ -806,7 +831,7 @@ csrf_token();
You can also get the token directly: You can also get the token directly:
```php ```php
return Router::router()->getCsrfVerifier()->getTokenProvider()->getToken(); return SimpleRouter::router()->getCsrfVerifier()->getTokenProvider()->getToken();
``` ```
The default name/key for the input-field is `csrf_token` and is defined in the `POST_KEY` constant in the `BaseCsrfVerifier` class. The default name/key for the input-field is `csrf_token` and is defined in the `POST_KEY` constant in the `BaseCsrfVerifier` class.
@@ -892,10 +917,10 @@ class SessionTokenProvider implements ITokenProvider
Next you need to set your custom `ITokenProvider` implementation on your `BaseCsrfVerifier` class in your routes file: Next you need to set your custom `ITokenProvider` implementation on your `BaseCsrfVerifier` class in your routes file:
```php ```php
$verifier = new \dscuz\Middleware\CsrfVerifier(); $verifier = new \Demo\Middlewares\CsrfVerifier();
$verifier->setTokenProvider(new SessionTokenProvider()); $verifier->setTokenProvider(new SessionTokenProvider());
Router::csrfVerifier($verifier); SimpleRouter::csrfVerifier($verifier);
``` ```
--- ---
@@ -937,7 +962,7 @@ ExceptionHandler are classes that handles all exceptions. ExceptionsHandlers mus
## Handling 404, 403 and other errors ## Handling 404, 403 and other errors
If you simply want to catch a 404 (page not found) etc. you can use the `Router::error($callback)` static helper method. If you simply want to catch a 404 (page not found) etc. you can use the `SimpleRouter::error($callback)` static helper method.
This will add a callback method which is fired whenever an error occurs on all routes. This will add a callback method which is fired whenever an error occurs on all routes.
@@ -945,17 +970,37 @@ The basic example below simply redirect the page to `/not-found` if an `NotFound
The code should be placed in the file that contains your routes. The code should be placed in the file that contains your routes.
```php ```php
Router::get('/not-found', 'PageController@notFound'); SimpleRouter::get('/not-found', 'PageController@notFound');
SimpleRouter::get('/forbidden', 'PageController@notFound');
Router::error(function(Request $request, \Exception $exception) { SimpleRouter::error(function(Request $request, \Exception $exception) {
if($exception instanceof NotFoundHttpException && $exception->getCode() === 404) { switch($exception->getCode()) {
response()->redirect('/not-found'); // Page not found
case 404:
response()->redirect('/not-found');
// Forbidden
case 403:
response()->redirect('/forbidden');
} }
}); });
``` ```
The example above will redirect all errors with http-code `404` (page not found) to `/not-found` and `403` (forbidden) to `/forbidden`.
If you do not want a redirect, but want the error-page rendered on the current-url, you can tell the router to execute a rewrite callback like so:
```php
$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 ## 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). 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).
@@ -991,6 +1036,17 @@ class CustomExceptionHandler implements IExceptionHandler
return; 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; throw $error;
@@ -999,6 +1055,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 # Urls
@@ -1236,8 +1327,11 @@ $values = input()->all([
All object implements the `IInputItem` interface and will always contain these methods: All object implements the `IInputItem` interface and will always contain these methods:
- `getIndex()` - returns the index/key of the input. - `getIndex()` - returns the index/key of the input.
- `setIndex()` - set the index/key of the input.
- `getName()` - returns a human friendly name for the input (company_name will be Company Name etc). - `getName()` - returns a human friendly name for the input (company_name will be Company Name etc).
- `setName()` - sets a human friendly name for the input (company_name will be Company Name etc).
- `getValue()` - returns the value of the input. - `getValue()` - returns the value of the input.
- `setValue()` - sets the value of the input.
`InputFile` has the same methods as above along with some other file-specific methods like: `InputFile` has the same methods as above along with some other file-specific methods like:
@@ -1253,6 +1347,24 @@ All object implements the `IInputItem` interface and will always contain these m
--- ---
### Check if parameters exists
You can easily if multiple items exists by using the `exists` method. It's simular to `value` as it can be used
to filter on request-methods and supports both `string` and `array` as parameter value.
**Example:**
```php
if(input()->exists(['name', 'lastname'])) {
// Do stuff
}
/* Similar to code above */
if(input()->exists('name') && input()->exists('lastname')) {
// Do stuff
}
```
# Events # Events
This section will help you understand how to register your own callbacks to events in the router. This section will help you understand how to register your own callbacks to events in the router.
@@ -1275,7 +1387,7 @@ All event callbacks will retrieve a `EventArgument` object as parameter. This ob
| `EVENT_RENDER_BOOTMANAGER` | `bootmanagers`<br>`bootmanager` | Fires before a boot-manager is rendered. | | `EVENT_RENDER_BOOTMANAGER` | `bootmanagers`<br>`bootmanager` | Fires before a boot-manager is rendered. |
| `EVENT_LOAD_ROUTES` | `routes` | Fires when the router is about to load all routes. | | `EVENT_LOAD_ROUTES` | `routes` | Fires when the router is about to load all routes. |
| `EVENT_FIND_ROUTE` | `name` | Fires whenever the `findRoute` method is called within the `Router`. This usually happens when the router tries to find routes that contains a certain url, usually after the `EventHandler::EVENT_GET_URL` event. | | `EVENT_FIND_ROUTE` | `name` | Fires whenever the `findRoute` method is called within the `Router`. This usually happens when the router tries to find routes that contains a certain url, usually after the `EventHandler::EVENT_GET_URL` event. |
| `EVENT_GET_URL` | `name`<br>`parameters`<br>`getParams` | Fires whenever the `Router::getUrl` method or `url`-helper function is called and the router tries to find the route. | | `EVENT_GET_URL` | `name`<br>`parameters`<br>`getParams` | Fires whenever the `SimpleRouter::getUrl` method or `url`-helper function is called and the router tries to find the route. |
| `EVENT_MATCH_ROUTE` | `route` | Fires when a route is matched and valid (correct request-type etc). and before the route is rendered. | | `EVENT_MATCH_ROUTE` | `route` | Fires when a route is matched and valid (correct request-type etc). and before the route is rendered. |
| `EVENT_RENDER_ROUTE` | `route` | Fires before a route is rendered. | | `EVENT_RENDER_ROUTE` | `route` | Fires before a route is rendered. |
| `EVENT_LOAD_EXCEPTIONS` | `exception`<br>`exceptionHandlers` | Fires when the router is loading exception-handlers. | | `EVENT_LOAD_EXCEPTIONS` | `exception`<br>`exceptionHandlers` | Fires when the router is loading exception-handlers. |
@@ -1391,11 +1503,12 @@ class DatabaseDebugHandler implements IEventHandler
# Advanced # 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 ## Restrict access to IP
@@ -1458,7 +1571,7 @@ $eventHandler->register(EventHandler::EVENT_ADD_ROUTE, function(EventArgument $e
}); });
TestRouter::addEventHandler($eventHandler); SimpleRouter::addEventHandler($eventHandler);
``` ```
In the example shown above, we create a new `EVENT_ADD_ROUTE` event that triggers, when a new route is added. In the example shown above, we create a new `EVENT_ADD_ROUTE` event that triggers, when a new route is added.
@@ -2001,4 +2114,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 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 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 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
+13 -4
View File
@@ -27,12 +27,16 @@
} }
], ],
"require": { "require": {
"php": ">=7.1", "php": ">=7.4",
"ext-json": "*" "ext-json": "*"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "^7", "phpunit/phpunit": "^8",
"mockery/mockery": "^1" "mockery/mockery": "^1",
"phpstan/phpstan": "^1",
"phpstan/phpstan-phpunit": "^1",
"phpstan/phpstan-deprecation-rules": "^1",
"phpstan/phpstan-strict-rules": "^1"
}, },
"scripts": { "scripts": {
"test": [ "test": [
@@ -43,5 +47,10 @@
"psr-4": { "psr-4": {
"Pecee\\": "src/Pecee/" "Pecee\\": "src/Pecee/"
} }
},
"config": {
"allow-plugins": {
"ocramius/package-versions": true
}
} }
} }
+22
View File
@@ -0,0 +1,22 @@
parameters:
level: 6
paths:
- src
fileExtensions:
- php
bootstrapFiles:
- ./vendor/autoload.php
ignoreErrors:
reportUnmatchedIgnoredErrors: true
checkMissingIterableValueType: false
checkGenericClassInNonGenericObjectType: false
parallel:
processTimeout: 300.0
jobSize: 10
maximumNumberOfProcesses: 4
minimumNumberOfJobsPerProcess: 4
includes:
- vendor/phpstan/phpstan-strict-rules/rules.neon
- vendor/phpstan/phpstan-phpunit/extension.neon
- vendor/phpstan/phpstan-phpunit/rules.neon
- vendor/phpstan/phpstan-deprecation-rules/rules.neon
+6
View File
@@ -13,8 +13,14 @@ interface IInputItem
public function setName(string $name): self; public function setName(string $name): self;
/**
* @return mixed
*/
public function getValue(); public function getValue();
/**
* @param mixed $value
*/
public function setValue($value): self; public function setValue($value): self;
public function __toString(): string; public function __toString(): string;
+37 -10
View File
@@ -6,13 +6,40 @@ use Pecee\Exceptions\InvalidArgumentException;
class InputFile implements IInputItem class InputFile implements IInputItem
{ {
public $index; /**
public $name; * @var string
public $filename; */
public $size; public string $index;
public $type;
public $errors; /**
public $tmpName; * @var string
*/
public string $name;
/**
* @var string|null
*/
public ?string $filename = null;
/**
* @var int|null
*/
public ?int $size = null;
/**
* @var string|null
*/
public ?string $type = null;
/**
* @var int
*/
public int $errors = 0;
/**
* @var string|null
*/
public ?string $tmpName = null;
public function __construct(string $index) public function __construct(string $index)
{ {
@@ -47,7 +74,7 @@ class InputFile implements IInputItem
'error' => null, 'error' => null,
]; ];
return (new static($values['index'])) return (new self($values['index']))
->setSize((int)$values['size']) ->setSize((int)$values['size'])
->setError((int)$values['error']) ->setError((int)$values['error'])
->setType($values['type']) ->setType($values['type'])
@@ -77,9 +104,9 @@ class InputFile implements IInputItem
} }
/** /**
* @return string * @return int
*/ */
public function getSize(): string public function getSize(): ?int
{ {
return $this->size; return $this->size;
} }
+24 -12
View File
@@ -10,40 +10,40 @@ class InputHandler
/** /**
* @var array * @var array
*/ */
protected $get = []; protected array $get = [];
/** /**
* @var array * @var array
*/ */
protected $post = []; protected array $post = [];
/** /**
* @var array * @var array
*/ */
protected $file = []; protected array $file = [];
/** /**
* @var Request * @var Request
*/ */
protected $request; protected Request $request;
/** /**
* Original post variables * Original post variables
* @var array * @var array
*/ */
protected $originalPost = []; protected array $originalPost = [];
/** /**
* Original get/params variables * Original get/params variables
* @var array * @var array
*/ */
protected $originalParams = []; protected array $originalParams = [];
/** /**
* Get original file variables * Get original file variables
* @var array * @var array
*/ */
protected $originalFile = []; protected array $originalFile = [];
/** /**
* Input constructor. * Input constructor.
@@ -115,7 +115,7 @@ class InputHandler
// Handle array input // Handle array input
if (is_array($value['name']) === false) { if (is_array($value['name']) === false) {
$values['index'] = $parentKey ?? $key; $values = ['index' => $parentKey ?? $key];
try { try {
$list[$key] = InputFile::createFromArray($values + $value); $list[$key] = InputFile::createFromArray($values + $value);
@@ -161,7 +161,7 @@ class InputHandler
try { try {
$file = InputFile::createFromArray([ $file = InputFile::createFromArray([
'index' => (empty($key) === true && empty($originalIndex) === false) ? $originalIndex : $key, 'index' => ($key === '' && $originalIndex !== '') ? $originalIndex : $key,
'name' => $original['name'][$key], 'name' => $original['name'][$key],
'error' => $original['error'][$key], 'error' => $original['error'][$key],
'tmp_name' => $original['tmp_name'][$key], 'tmp_name' => $original['tmp_name'][$key],
@@ -293,14 +293,26 @@ class InputHandler
} }
/** /**
* Check if a input-item exist * Check if a input-item exist.
* If an array is as $index parameter the method returns true if all elements exist.
* *
* @param string $index * @param string|array $index
* @param array ...$methods * @param array ...$methods
* @return bool * @return bool
*/ */
public function exists(string $index, ...$methods): bool public function exists($index, ...$methods): bool
{ {
// Check array
if(is_array($index) === true) {
foreach($index as $key) {
if($this->value($key, null, ...$methods) === null) {
return false;
}
}
return true;
}
return $this->value($index, null, ...$methods) !== null; return $this->value($index, null, ...$methods) !== null;
} }
+13 -4
View File
@@ -8,10 +8,18 @@ use IteratorAggregate;
class InputItem implements ArrayAccess, IInputItem, IteratorAggregate class InputItem implements ArrayAccess, IInputItem, IteratorAggregate
{ {
public $index; public string $index;
public $name; public string $name;
/**
* @var mixed|null
*/
public $value; public $value;
/**
* @param string $index
* @param mixed $value
*/
public function __construct(string $index, $value = null) public function __construct(string $index, $value = null)
{ {
$this->index = $index; $this->index = $index;
@@ -81,7 +89,8 @@ class InputItem implements ArrayAccess, IInputItem, IteratorAggregate
return isset($this->value[$offset]); return isset($this->value[$offset]);
} }
public function offsetGet($offset) #[\ReturnTypeWillChange]
public function offsetGet($offset): ?self
{ {
if ($this->offsetExists($offset) === true) { if ($this->offsetExists($offset) === true) {
return $this->value[$offset]; return $this->value[$offset];
@@ -97,7 +106,7 @@ class InputItem implements ArrayAccess, IInputItem, IteratorAggregate
public function offsetUnset($offset): void public function offsetUnset($offset): void
{ {
unset($this->data[$offset]); unset($this->value[$offset]);
} }
public function __toString(): string public function __toString(): string
+30 -20
View File
@@ -17,13 +17,18 @@ class BaseCsrfVerifier implements IMiddleware
* For example: /admin/* * For example: /admin/*
* @var array|null * @var array|null
*/ */
protected $except; protected array $except = [];
/** /**
* Urls to include. Can be used to include urls from a certain path. * Urls to include. Can be used to include urls from a certain path.
* @var array|null * @var array|null
*/ */
protected $include; protected array $include = [];
protected $tokenProvider;
/**
* @var ITokenProvider
*/
protected ITokenProvider $tokenProvider;
/** /**
* BaseCsrfVerifier constructor. * BaseCsrfVerifier constructor.
@@ -33,6 +38,23 @@ class BaseCsrfVerifier implements IMiddleware
$this->tokenProvider = new CookieTokenProvider(); $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 * Check if the url matches the urls in the except property
* @param Request $request * @param Request $request
@@ -40,11 +62,11 @@ class BaseCsrfVerifier implements IMiddleware
*/ */
protected function skip(Request $request): bool protected function skip(Request $request): bool
{ {
if ($this->except === null || count($this->except) === 0) { if (count($this->except) === 0) {
return false; return false;
} }
foreach($this->except as $url) { foreach ($this->except as $url) {
$url = rtrim($url, '/'); $url = rtrim($url, '/');
if ($url[strlen($url) - 1] === '*') { if ($url[strlen($url) - 1] === '*') {
$url = rtrim($url, '*'); $url = rtrim($url, '*');
@@ -55,20 +77,9 @@ class BaseCsrfVerifier implements IMiddleware
if ($skip === true) { if ($skip === true) {
if(is_array($this->include) === true && count($this->include) > 0) { $skip = !$this->isIncluded($request);
foreach($this->include as $includeUrl) {
$includeUrl = rtrim($includeUrl, '/');
if ($includeUrl[strlen($includeUrl) - 1] === '*') {
$includeUrl = rtrim($includeUrl, '*');
$skip = !$request->getUrl()->contains($includeUrl);
break;
}
$skip = !($includeUrl === rtrim($request->getUrl()->getRelativeUrl(false), '/')); if ($skip === false) {
}
}
if($skip === false) {
continue; continue;
} }
@@ -87,12 +98,11 @@ class BaseCsrfVerifier implements IMiddleware
*/ */
public function handle(Request $request): void public function handle(Request $request): void
{ {
if ($this->skip($request) === false && $request->isPostBack() === true) { if ($this->skip($request) === false && ($request->isPostBack() === true || $this->isIncluded($request) === true)) {
$token = $request->getInputHandler()->value( $token = $request->getInputHandler()->value(
static::POST_KEY, static::POST_KEY,
$request->getHeader(static::HEADER_KEY), $request->getHeader(static::HEADER_KEY),
Request::$requestTypesPost
); );
if ($this->tokenProvider->validate((string)$token) === false) { if ($this->tokenProvider->validate((string)$token) === false) {
@@ -7,8 +7,8 @@ use Pecee\SimpleRouter\Exceptions\HttpException;
abstract class IpRestrictAccess implements IMiddleware abstract class IpRestrictAccess implements IMiddleware
{ {
protected $ipBlacklist = []; protected array $ipBlacklist = [];
protected $ipWhitelist = []; protected array $ipWhitelist = [];
protected function validate(string $ip): bool protected function validate(string $ip): bool
{ {
+29 -22
View File
@@ -29,7 +29,7 @@ class Request
* All request-types * All request-types
* @var string[] * @var string[]
*/ */
public static $requestTypes = [ public static array $requestTypes = [
self::REQUEST_TYPE_GET, self::REQUEST_TYPE_GET,
self::REQUEST_TYPE_POST, self::REQUEST_TYPE_POST,
self::REQUEST_TYPE_PUT, self::REQUEST_TYPE_PUT,
@@ -43,7 +43,7 @@ class Request
* Post request-types. * Post request-types.
* @var string[] * @var string[]
*/ */
public static $requestTypesPost = [ public static array $requestTypesPost = [
self::REQUEST_TYPE_POST, self::REQUEST_TYPE_POST,
self::REQUEST_TYPE_PUT, self::REQUEST_TYPE_PUT,
self::REQUEST_TYPE_PATCH, self::REQUEST_TYPE_PATCH,
@@ -55,65 +55,65 @@ class Request
* *
* @var array * @var array
*/ */
private $data = []; private array $data = [];
/** /**
* Server headers * Server headers
* @var array * @var array
*/ */
protected $headers = []; protected array $headers = [];
/** /**
* Request ContentType * Request ContentType
* @var string * @var string
*/ */
protected $contentType; protected string $contentType;
/** /**
* Request host * Request host
* @var string * @var string|null
*/ */
protected $host; protected ?string $host;
/** /**
* Current request url * Current request url
* @var Url * @var Url
*/ */
protected $url; protected Url $url;
/** /**
* Request method * Request method
* @var string * @var string
*/ */
protected $method; protected string $method;
/** /**
* Input handler * Input handler
* @var InputHandler * @var InputHandler
*/ */
protected $inputHandler; protected InputHandler $inputHandler;
/** /**
* Defines if request has pending rewrite * Defines if request has pending rewrite
* @var bool * @var bool
*/ */
protected $hasPendingRewrite = false; protected bool $hasPendingRewrite = false;
/** /**
* @var ILoadableRoute|null * @var ILoadableRoute|null
*/ */
protected $rewriteRoute; protected ?ILoadableRoute $rewriteRoute = null;
/** /**
* Rewrite url * Rewrite url
* @var string|null * @var string|null
*/ */
protected $rewriteUrl; protected ?string $rewriteUrl = null;
/** /**
* @var array * @var array
*/ */
protected $loadedRoutes = []; protected array $loadedRoutes = [];
/** /**
* Request constructor. * Request constructor.
@@ -129,7 +129,12 @@ class Request
$this->setHost($this->getHeader('http-host')); $this->setHost($this->getHeader('http-host'));
// Check if special IIS header exist, otherwise use default. // 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->setContentType((string)$this->getHeader('content-type'));
$this->setMethod((string)($_POST[static::FORCE_METHOD_KEY] ?? $this->getHeader('request-method'))); $this->setMethod((string)($_POST[static::FORCE_METHOD_KEY] ?? $this->getHeader('request-method')));
$this->inputHandler = new InputHandler($this); $this->inputHandler = new InputHandler($this);
@@ -137,7 +142,7 @@ class Request
public function isSecure(): bool public function isSecure(): bool
{ {
return $this->getHeader('http-x-forwarded-proto') === 'https' || $this->getHeader('https') !== null || $this->getHeader('server-port') === 443; return $this->getHeader('http-x-forwarded-proto') === 'https' || $this->getHeader('https') !== null || (int)$this->getHeader('server-port') === 443;
} }
/** /**
@@ -219,15 +224,17 @@ class Request
*/ */
public function getIp(bool $safeMode = false): ?string public function getIp(bool $safeMode = false): ?string
{ {
$headers = ['remote-addr']; $headers = [];
if($safeMode === false) { if($safeMode === false) {
$headers = array_merge($headers, [ $headers = [
'http-cf-connecting-ip', 'http-cf-connecting-ip',
'http-client-ip', 'http-client-ip',
'http-x-forwarded-for', 'http-x-forwarded-for',
]); ];
} }
$headers[] = 'remote-addr';
return $this->getFirstHeader($headers); return $this->getFirstHeader($headers);
} }
@@ -264,12 +271,12 @@ class Request
* Get header value by name * Get header value by name
* *
* @param string $name Name of the header. * @param string $name Name of the header.
* @param string|null $defaultValue Value to be returned if header is not found. * @param string|mixed|null $defaultValue Value to be returned if header is not found.
* @param bool $tryParse When enabled the method will try to find the header from both from client (http) and server-side variants, if the header is not found. * @param bool $tryParse When enabled the method will try to find the header from both from client (http) and server-side variants, if the header is not found.
* *
* @return string|null * @return string|null
*/ */
public function getHeader(string $name, $defaultValue = null, $tryParse = true): ?string public function getHeader(string $name, $defaultValue = null, bool $tryParse = true): ?string
{ {
$name = strtolower($name); $name = strtolower($name);
$header = $this->headers[$name] ?? null; $header = $this->headers[$name] ?? null;
@@ -359,7 +366,7 @@ class Request
*/ */
public function isAjax(): bool public function isAjax(): bool
{ {
return (strtolower($this->getHeader('http-x-requested-with')) === 'xmlhttprequest'); return (strtolower((string)$this->getHeader('http-x-requested-with')) === 'xmlhttprequest');
} }
/** /**
+3 -4
View File
@@ -7,7 +7,7 @@ use Pecee\Exceptions\InvalidArgumentException;
class Response class Response
{ {
protected $request; protected Request $request;
public function __construct(Request $request) public function __construct(Request $request)
{ {
@@ -65,7 +65,6 @@ class Response
public function cache(string $eTag, int $lastModifiedTime = 2592000): self public function cache(string $eTag, int $lastModifiedTime = 2592000): self
{ {
$this->headers([ $this->headers([
'Cache-Control: public', 'Cache-Control: public',
sprintf('Last-Modified: %s GMT', gmdate('D, d M Y H:i:s', $lastModifiedTime)), sprintf('Last-Modified: %s GMT', gmdate('D, d M Y H:i:s', $lastModifiedTime)),
@@ -87,11 +86,11 @@ class Response
/** /**
* Json encode * Json encode
* @param array|JsonSerializable $value * @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. * @param int $dept JSON debt.
* @throws InvalidArgumentException * @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) { 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.'); throw new InvalidArgumentException('Invalid type for parameter "value". Must be of type array or object implementing the \JsonSerializable interface.');
@@ -9,8 +9,15 @@ class CookieTokenProvider implements ITokenProvider
{ {
public const CSRF_KEY = 'CSRF-TOKEN'; public const CSRF_KEY = 'CSRF-TOKEN';
protected $token; /**
protected $cookieTimeoutMinutes = 120; * @var string
*/
protected ?string $token = null;
/**
* @var int
*/
protected int $cookieTimeoutMinutes = 120;
/** /**
* CookieTokenProvider constructor. * CookieTokenProvider constructor.
+67 -17
View File
@@ -7,16 +7,56 @@ use Pecee\Http\Exceptions\MalformedUrlException;
class Url implements JsonSerializable class Url implements JsonSerializable
{ {
private $originalUrl; /**
* @var string|null
*/
private ?string $originalUrl = null;
private $scheme; /**
private $host; * @var string|null
private $port; */
private $username; private ?string $scheme = null;
private $password;
private $path; /**
private $params = []; * @var string|null
private $fragment; */
private ?string $host = null;
/**
* @var int|null
*/
private ?int $port = null;
/**
* @var string|null
*/
private ?string $username = null;
/**
* @var string|null
*/
private ?string $password = null;
/**
* @var string|null
*/
private ?string $path = null;
/**
* Original path with no sanitization to ending slash
* @var string|null
*/
private ?string $originalPath = null;
/**
* @var array
*/
private array $params = [];
/**
* @var string|null
*/
private ?string $fragment = null;
/** /**
* Url constructor. * Url constructor.
@@ -39,6 +79,7 @@ class Url implements JsonSerializable
if (isset($data['path']) === true) { if (isset($data['path']) === true) {
$this->setPath($data['path']); $this->setPath($data['path']);
$this->originalPath = $data['path'];
} }
$this->fragment = $data['fragment'] ?? null; $this->fragment = $data['fragment'] ?? null;
@@ -192,6 +233,15 @@ class Url implements JsonSerializable
return $this->path ?? '/'; 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 * Set the url path
* *
@@ -250,7 +300,7 @@ class Url implements JsonSerializable
$params = []; $params = [];
parse_str($queryString, $params); parse_str($queryString, $params);
if(count($params) > 0) { if (count($params) > 0) {
return $this->setParams($params); return $this->setParams($params);
} }
@@ -387,7 +437,7 @@ class Url implements JsonSerializable
{ {
$encodedUrl = preg_replace_callback( $encodedUrl = preg_replace_callback(
'/[^:\/@?&=#]+/u', '/[^:\/@?&=#]+/u',
static function ($matches) { static function ($matches): string {
return urlencode($matches[0]); return urlencode($matches[0]);
}, },
$url $url
@@ -414,7 +464,7 @@ class Url implements JsonSerializable
if (count($getParams) !== 0) { if (count($getParams) !== 0) {
if ($includeEmpty === false) { if ($includeEmpty === false) {
$getParams = array_filter($getParams, static function ($item) { $getParams = array_filter($getParams, static function ($item): bool {
return (trim($item) !== ''); return (trim($item) !== '');
}); });
} }
@@ -431,11 +481,11 @@ class Url implements JsonSerializable
* @param bool $includeParams * @param bool $includeParams
* @return string * @return string
*/ */
public function getRelativeUrl($includeParams = true): string public function getRelativeUrl(bool $includeParams = true): string
{ {
$path = $this->path ?? '/'; $path = $this->path ?? '/';
if($includeParams === false) { if ($includeParams === false) {
return $path; return $path;
} }
@@ -451,14 +501,14 @@ class Url implements JsonSerializable
* @param bool $includeParams * @param bool $includeParams
* @return string * @return string
*/ */
public function getAbsoluteUrl($includeParams = true): string public function getAbsoluteUrl(bool $includeParams = true): string
{ {
$scheme = $this->scheme !== null ? $this->scheme . '://' : ''; $scheme = $this->scheme !== null ? $this->scheme . '://' : '';
$host = $this->host ?? ''; $host = $this->host ?? '';
$port = $this->port !== null ? ':' . $this->port : ''; $port = $this->port !== null ? ':' . $this->port : '';
$user = $this->username ?? ''; $user = $this->username ?? '';
$pass = $this->password !== null ? ':' . $this->password : ''; $pass = $this->password !== null ? ':' . $this->password : '';
$pass = ($user || $pass) ? $pass . '@' : ''; $pass = ($user !== '' || $pass !== '') ? $pass . '@' : '';
return $scheme . $user . $pass . $host . $port . $this->getRelativeUrl($includeParams); return $scheme . $user . $pass . $host . $port . $this->getRelativeUrl($includeParams);
} }
@@ -466,7 +516,7 @@ class Url implements JsonSerializable
/** /**
* Specify data which should be serialized to JSON * Specify data which should be serialized to JSON
* @link http://php.net/manual/en/jsonserializable.jsonserialize.php * @link http://php.net/manual/en/jsonserializable.jsonserialize.php
* @return mixed data which can be serialized by <b>json_encode</b>, * @return string data which can be serialized by <b>json_encode</b>,
* which is a value of any type other than a resource. * which is a value of any type other than a resource.
* @since 5.4.0 * @since 5.4.0
*/ */
@@ -27,11 +27,11 @@ class ClassLoader implements IClassLoader
* @param object $class * @param object $class
* @param string $method * @param string $method
* @param array $parameters * @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 Callable $closure
* @param array $parameters * @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 object $class
* @param string $method * @param string $method
* @param array $parameters * @param array $parameters
* @return object * @return mixed
*/ */
public function loadClassMethod($class, string $method, array $parameters); public function loadClassMethod($class, string $method, array $parameters);
@@ -12,19 +12,19 @@ class EventArgument implements IEventArgument
* Event name * Event name
* @var string * @var string
*/ */
protected $eventName; protected string $eventName;
/** /**
* @var Router * @var Router
*/ */
protected $router; protected Router $router;
/** /**
* @var array * @var array
*/ */
protected $arguments = []; protected array $arguments = [];
public function __construct($eventName, $router, array $arguments = []) public function __construct(string $eventName, Router $router, array $arguments = [])
{ {
$this->eventName = $eventName; $this->eventName = $eventName;
$this->router = $router; $this->router = $router;
@@ -94,7 +94,7 @@ class EventArgument implements IEventArgument
* @param mixed $value * @param mixed $value
* @throws InvalidArgumentException * @throws InvalidArgumentException
*/ */
public function __set(string $name, $value) public function __set(string $name, $value): void
{ {
throw new InvalidArgumentException('Not supported'); throw new InvalidArgumentException('Not supported');
} }
@@ -6,10 +6,17 @@ use Throwable;
class ClassNotFoundHttpException extends NotFoundHttpException class ClassNotFoundHttpException extends NotFoundHttpException
{ {
protected $class; /**
protected $method; * @var string
*/
protected string $class;
public function __construct(string $class, ?string $method = null, $message = "", $code = 0, Throwable $previous = null) /**
* @var string|null
*/
protected ?string $method = null;
public function __construct(string $class, ?string $method = null, string $message = "", int $code = 0, Throwable $previous = null)
{ {
parent::__construct($message, $code, $previous); parent::__construct($message, $code, $previous);
@@ -17,7 +17,10 @@ use Pecee\Http\Request;
class CallbackExceptionHandler implements IExceptionHandler class CallbackExceptionHandler implements IExceptionHandler
{ {
protected $callback; /**
* @var Closure
*/
protected Closure $callback;
public function __construct(Closure $callback) public function __construct(Closure $callback)
{ {
@@ -13,11 +13,11 @@ class DebugEventHandler implements IEventHandler
* Debug callback * Debug callback
* @var Closure * @var Closure
*/ */
protected $callback; protected Closure $callback;
public function __construct() public function __construct()
{ {
$this->callback = static function (EventArgument $argument) { $this->callback = static function (EventArgument $argument): void {
// todo: log in database // todo: log in database
}; };
} }
@@ -47,7 +47,7 @@ class DebugEventHandler implements IEventHandler
public function fireEvents(Router $router, string $name, array $eventArgs = []): void public function fireEvents(Router $router, string $name, array $eventArgs = []): void
{ {
$callback = $this->callback; $callback = $this->callback;
$callback(new EventArgument($router, $eventArgs)); $callback(new EventArgument($name, $router, $eventArgs));
} }
/** /**
@@ -97,7 +97,7 @@ class EventHandler implements IEventHandler
* All available events * All available events
* @var array * @var array
*/ */
public static $events = [ public static array $events = [
self::EVENT_ALL, self::EVENT_ALL,
self::EVENT_INIT, self::EVENT_INIT,
self::EVENT_LOAD, self::EVENT_LOAD,
@@ -120,7 +120,7 @@ class EventHandler implements IEventHandler
* List of all registered events * List of all registered events
* @var array * @var array
*/ */
private $registeredEvents = []; private array $registeredEvents = [];
/** /**
* Register new event * Register new event
@@ -2,7 +2,7 @@
namespace Pecee\SimpleRouter\Route; namespace Pecee\SimpleRouter\Route;
interface IControllerRoute extends IRoute interface IControllerRoute extends ILoadableRoute
{ {
/** /**
* Get controller class-name * Get controller class-name
@@ -31,6 +31,21 @@ interface IGroupRoute extends IRoute
*/ */
public function setExceptionHandlers(array $handlers): self; 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 * Get exception-handlers for group
* *
+29 -7
View File
@@ -12,14 +12,17 @@ abstract class LoadableRoute extends Route implements ILoadableRoute
/** /**
* @var string * @var string
*/ */
protected $url; protected string $url;
/** /**
* @var string * @var string
*/ */
protected $name; protected ?string $name = null;
protected $regex; /**
* @var string|null
*/
protected ?string $regex = null;
/** /**
* Loads and renders middlewares-classes * Loads and renders middlewares-classes
@@ -59,7 +62,14 @@ abstract class LoadableRoute extends Route implements ILoadableRoute
return null; return null;
} }
return ((bool)preg_match($this->regex, $url) !== false); $parameters = [];
if ((bool)preg_match($this->regex, $url, $parameters) !== false) {
$this->setParameters($parameters);
return true;
}
return false;
} }
/** /**
@@ -100,6 +110,18 @@ abstract class LoadableRoute extends Route implements ILoadableRoute
return $this->url; return $this->url;
} }
/**
* Returns true if group is defined and matches the given url.
*
* @param string $url
* @param Request $request
* @return bool
*/
protected function matchGroup(string $url, Request $request): bool
{
return ($this->getGroup() === null || $this->getGroup()->matchRoute($url, $request) === true);
}
/** /**
* Find url that matches method, parameters or name. * Find url that matches method, parameters or name.
* Used when calling the url() helper. * Used when calling the url() helper.
@@ -145,7 +167,7 @@ abstract class LoadableRoute extends Route implements ILoadableRoute
if (stripos($url, $param1) !== false || stripos($url, $param) !== false) { if (stripos($url, $param1) !== false || stripos($url, $param) !== false) {
/* Add parameter to the correct position */ /* 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 { } else {
/* Parameter aren't recognized and will be appended at the end of the url */ /* Parameter aren't recognized and will be appended at the end of the url */
$url .= $value . '/'; $url .= $value . '/';
@@ -173,7 +195,7 @@ abstract class LoadableRoute extends Route implements ILoadableRoute
*/ */
public function hasName(string $name): bool public function hasName(string $name): bool
{ {
return strtolower($this->name) === strtolower($name); return strtolower((string)$this->name) === strtolower($name);
} }
/** /**
@@ -203,9 +225,9 @@ abstract class LoadableRoute extends Route implements ILoadableRoute
* Sets the router name, which makes it easier to obtain the url or router at a later point. * Sets the router name, which makes it easier to obtain the url or router at a later point.
* Alias for LoadableRoute::setName(). * Alias for LoadableRoute::setName().
* *
* @see LoadableRoute::setName()
* @param string|array $name * @param string|array $name
* @return static * @return static
* @see LoadableRoute::setName()
*/ */
public function name($name): ILoadableRoute public function name($name): ILoadableRoute
{ {
+67 -22
View File
@@ -18,28 +18,37 @@ abstract class Route implements IRoute
* *
* @var bool * @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. * Default regular expression used for parsing parameters.
* @var string|null * @var string|null
*/ */
protected $defaultParameterRegex; protected ?string $defaultParameterRegex = null;
protected $paramModifiers = '{}'; protected string $paramModifiers = '{}';
protected $paramOptionalSymbol = '?'; protected string $paramOptionalSymbol = '?';
protected $urlRegex = '/^%s\/?$/u'; protected string $urlRegex = '/^%s\/?$/u';
protected $group; protected ?IGroupRoute $group = null;
protected $parent; protected ?IRoute $parent = null;
/**
* @var string|callable|null
*/
protected $callback; protected $callback;
protected $defaultNamespace; protected ?string $defaultNamespace = null;
/* Default options */ /* Default options */
protected $namespace; protected ?string $namespace = null;
protected $requestMethods = []; protected array $requestMethods = [];
protected $where = []; protected array $where = [];
protected $parameters = []; protected array $parameters = [];
protected $originalParameters = []; protected array $originalParameters = [];
protected $middlewares = []; protected array $middlewares = [];
/** /**
* Render route * Render route
@@ -67,7 +76,7 @@ abstract class Route implements IRoute
/* Filter parameters with null-value */ /* Filter parameters with null-value */
if ($this->filterEmptyParams === true) { if ($this->filterEmptyParams === true) {
$parameters = array_filter($parameters, static function ($var) { $parameters = array_filter($parameters, static function ($var): bool {
return ($var !== null); return ($var !== null);
}); });
} }
@@ -82,6 +91,7 @@ abstract class Route implements IRoute
} }
/* When the callback is a function */ /* When the callback is a function */
return $router->getClassLoader()->loadClosure($callback, $parameters); return $router->getClassLoader()->loadClosure($callback, $parameters);
} }
@@ -107,7 +117,7 @@ abstract class Route implements IRoute
return $router->getClassLoader()->loadClassMethod($class, $method, $parameters); 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 : $regex = (strpos($route, $this->paramModifiers[0]) === false) ? null :
sprintf sprintf
@@ -119,7 +129,10 @@ abstract class Route implements IRoute
); );
// Ensures that host names/domains will work with parameters // Ensures that host names/domains will work with parameters
$url = '/' . ltrim($url, '/'); if ($route[0] === $this->paramModifiers[0]) {
$url = '/' . ltrim($url, '/');
}
$urlRegex = ''; $urlRegex = '';
$parameters = []; $parameters = [];
@@ -127,7 +140,7 @@ abstract class Route implements IRoute
$urlRegex = preg_quote($route, '/'); $urlRegex = preg_quote($route, '/');
} else { } else {
foreach (preg_split('/((-?\/?){[^}]+})/', $route) as $key => $t) { foreach (preg_split('/((\.?-?\/?){[^' . $this->paramModifiers[1] . ']+' . $this->paramModifiers[1] . ')/', $route) as $key => $t) {
$regex = ''; $regex = '';
@@ -142,13 +155,14 @@ abstract class Route implements IRoute
$regex = $parameterRegex ?? $this->defaultParameterRegex ?? static::PARAMETERS_DEFAULT_REGEX; $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; $urlRegex .= preg_quote($t, '/') . $regex;
} }
} }
// Get name of last param
if (trim($urlRegex) === '' || (bool)preg_match(sprintf($this->urlRegex, $urlRegex), $url, $matches) === false) { if (trim($urlRegex) === '' || (bool)preg_match(sprintf($this->urlRegex, $urlRegex), $url, $matches) === false) {
return null; return null;
} }
@@ -162,7 +176,8 @@ abstract class Route implements IRoute
$lastParams = []; $lastParams = [];
/* Only take matched parameters with name */ /* 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 // Ignore parent parameters
if (isset($groupParameters[$name]) === true) { if (isset($groupParameters[$name]) === true) {
@@ -170,10 +185,16 @@ abstract class Route implements IRoute
continue; 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[$name] = (isset($matches[$name]) === true && $matches[$name] !== '') ? $matches[$name] : null;
} }
$values = array_merge($values, $lastParams); $values += $lastParams;
} }
$this->originalParameters = $values; $this->originalParameters = $values;
@@ -280,7 +301,7 @@ abstract class Route implements IRoute
} }
/** /**
* @return string|callable * @return string|callable|null
*/ */
public function getCallback() public function getCallback()
{ {
@@ -337,6 +358,11 @@ abstract class Route implements IRoute
*/ */
public function setNamespace(string $namespace): IRoute public function setNamespace(string $namespace): IRoute
{ {
// Do not set namespace when class-hinting is used
if (is_array($this->callback) === true) {
return $this;
}
$ns = $this->getNamespace(); $ns = $this->getNamespace();
if ($ns !== null) { if ($ns !== null) {
@@ -377,6 +403,17 @@ abstract class Route implements IRoute
return $this->namespace ?? $this->defaultNamespace; 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. * Export route settings to array so they can be merged with another route.
* *
@@ -406,6 +443,10 @@ abstract class Route implements IRoute
$values['defaultParameterRegex'] = $this->defaultParameterRegex; $values['defaultParameterRegex'] = $this->defaultParameterRegex;
} }
if ($this->slashParameterEnabled === true) {
$values['includeSlash'] = $this->slashParameterEnabled;
}
return $values; return $values;
} }
@@ -443,6 +484,10 @@ abstract class Route implements IRoute
$this->setDefaultParameterRegex($settings['defaultParameterRegex']); $this->setDefaultParameterRegex($settings['defaultParameterRegex']);
} }
if (isset($settings['includeSlash']) === true) {
$this->setSlashParameterEnabled($settings['includeSlash']);
}
return $this; return $this;
} }
@@ -6,10 +6,10 @@ use Pecee\Http\Request;
class RouteController extends LoadableRoute implements IControllerRoute class RouteController extends LoadableRoute implements IControllerRoute
{ {
protected $defaultMethod = 'index'; protected string $defaultMethod = 'index';
protected $controller; protected string $controller;
protected $method; protected ?string $method = null;
protected $names = []; protected array $names = [];
public function __construct($url, $controller) public function __construct($url, $controller)
{ {
@@ -52,7 +52,7 @@ class RouteController extends LoadableRoute implements IControllerRoute
public function findUrl(?string $method = null, $parameters = null, ?string $name = null): string public function findUrl(?string $method = null, $parameters = null, ?string $name = null): string
{ {
if (strpos($name, '.') !== false) { if (strpos($name, '.') !== false) {
$found = array_search(substr($name, strrpos($name, '.') + 1), $this->names, false); $found = array_search(substr($name, strrpos($name, '.') + 1), $this->names, true);
if ($found !== false) { if ($found !== false) {
$method = (string)$found; $method = (string)$found;
} }
@@ -67,7 +67,7 @@ class RouteController extends LoadableRoute implements IControllerRoute
foreach (Request::$requestTypes as $requestType) { foreach (Request::$requestTypes as $requestType) {
if (stripos($method, $requestType) === 0) { if (stripos($method, $requestType) === 0) {
$method = (string)substr($method, strlen($requestType)); $method = substr($method, strlen($requestType));
break; break;
} }
} }
@@ -81,14 +81,14 @@ class RouteController extends LoadableRoute implements IControllerRoute
$url .= '//' . $group->getDomains()[0]; $url .= '//' . $group->getDomains()[0];
} }
$url .= '/' . trim($this->getUrl(), '/') . '/' . strtolower($method) . implode('/', $parameters); $url .= '/' . trim($this->getUrl(), '/') . '/' . strtolower((string)$method) . implode('/', $parameters);
return '/' . trim($url, '/') . '/'; return '/' . trim($url, '/') . '/';
} }
public function matchRoute(string $url, Request $request): bool public function matchRoute(string $url, Request $request): bool
{ {
if ($this->getGroup() !== null && $this->getGroup()->matchRoute($url, $request) === false) { if ($this->matchGroup($url, $request) === false) {
return false; return false;
} }
+39 -10
View File
@@ -7,11 +7,12 @@ use Pecee\SimpleRouter\Handlers\IExceptionHandler;
class RouteGroup extends Route implements IGroupRoute class RouteGroup extends Route implements IGroupRoute
{ {
protected $urlRegex = '/^%s\/?/u'; protected string $urlRegex = '/^%s\/?/u';
protected $prefix; protected ?string $prefix = null;
protected $name; protected ?string $name = null;
protected $domains = []; protected array $domains = [];
protected $exceptionHandlers = []; protected array $exceptionHandlers = [];
protected bool $mergeExceptionHandlers = true;
/** /**
* Method called to check if a domain matches * Method called to check if a domain matches
@@ -21,7 +22,7 @@ class RouteGroup extends Route implements IGroupRoute
*/ */
public function matchDomain(Request $request): bool public function matchDomain(Request $request): bool
{ {
if ($this->domains === null || count($this->domains) === 0) { if (count($this->domains) === 0) {
return true; return true;
} }
@@ -32,10 +33,11 @@ class RouteGroup extends Route implements IGroupRoute
return true; return true;
} }
$parameters = $this->parseParameters($domain, $request->getHost(), '.*'); $parameters = $this->parseParameters($domain, $request->getHost(), $request, '.*');
if ($parameters !== null && count($parameters) !== 0) { if ($parameters !== null && count($parameters) !== 0) {
$this->parameters = $parameters; $this->parameters = $parameters;
return true; return true;
} }
} }
@@ -58,7 +60,7 @@ class RouteGroup extends Route implements IGroupRoute
if ($this->prefix !== null) { if ($this->prefix !== null) {
/* Parse parameters from current route */ /* 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 no custom regular expression or parameters was found on this route, we stop */
if ($parameters === null) { if ($parameters === null) {
@@ -72,11 +74,11 @@ class RouteGroup extends Route implements IGroupRoute
$parsedPrefix = $this->prefix; $parsedPrefix = $this->prefix;
foreach ($this->getParameters() as $parameter => $value) { 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 */ /* 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; return false;
} }
@@ -174,6 +176,29 @@ class RouteGroup extends Route implements IGroupRoute
return $this->prefix; 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. * Merge with information from another route.
* *
@@ -187,6 +212,10 @@ class RouteGroup extends Route implements IGroupRoute
$this->setPrefix($settings['prefix'] . $this->prefix); $this->setPrefix($settings['prefix'] . $this->prefix);
} }
if (isset($settings['mergeExceptionHandlers']) === true) {
$this->setMergeExceptionHandlers($settings['mergeExceptionHandlers']);
}
if ($merge === false && isset($settings['exceptionHandler']) === true) { if ($merge === false && isset($settings['exceptionHandler']) === true) {
$this->setExceptionHandlers((array)$settings['exceptionHandler']); $this->setExceptionHandlers((array)$settings['exceptionHandler']);
} }
+33 -30
View File
@@ -6,28 +6,28 @@ use Pecee\Http\Request;
class RouteResource extends LoadableRoute implements IControllerRoute class RouteResource extends LoadableRoute implements IControllerRoute
{ {
protected $urls = [ protected array $urls = [
'index' => '', 'index' => '',
'create' => 'create', 'create' => 'create',
'store' => '', 'store' => '',
'show' => '', 'show' => '',
'edit' => 'edit', 'edit' => 'edit',
'update' => '', 'update' => '',
'destroy' => '', 'destroy' => '',
]; ];
protected $methodNames = [ protected array $methodNames = [
'index' => 'index', 'index' => 'index',
'create' => 'create', 'create' => 'create',
'store' => 'store', 'store' => 'store',
'show' => 'show', 'show' => 'show',
'edit' => 'edit', 'edit' => 'edit',
'update' => 'update', 'update' => 'update',
'destroy' => 'destroy', 'destroy' => 'destroy',
]; ];
protected $names = []; protected array $names = [];
protected $controller; protected string $controller;
public function __construct($url, $controller) public function __construct($url, $controller)
{ {
@@ -54,7 +54,7 @@ class RouteResource extends LoadableRoute implements IControllerRoute
/* Remove method/type */ /* Remove method/type */
if (strpos($name, '.') !== false) { if (strpos($name, '.') !== false) {
$name = (string)substr($name, 0, strrpos($name, '.')); $name = substr($name, 0, strrpos($name, '.'));
} }
return (strtolower($this->name) === strtolower($name)); return (strtolower($this->name) === strtolower($name));
@@ -68,12 +68,15 @@ class RouteResource extends LoadableRoute implements IControllerRoute
*/ */
public function findUrl(?string $method = null, $parameters = null, ?string $name = null): string public function findUrl(?string $method = null, $parameters = null, ?string $name = null): string
{ {
$url = array_search($name, $this->names, false); $url = parent::findUrl($method, $parameters, $name);
if ($url !== false) {
return rtrim($this->url . $this->urls[$url], '/') . '/'; $action = array_search($name, $this->names, true);
if ($action !== false) {
return $url . $this->urls[$action];
} }
return $this->url; return $url;
} }
protected function call($method): bool protected function call($method): bool
@@ -85,7 +88,7 @@ class RouteResource extends LoadableRoute implements IControllerRoute
public function matchRoute(string $url, Request $request): bool public function matchRoute(string $url, Request $request): bool
{ {
if ($this->getGroup() !== null && $this->getGroup()->matchRoute($url, $request) === false) { if ($this->matchGroup($url, $request) === false) {
return false; return false;
} }
@@ -99,14 +102,14 @@ class RouteResource extends LoadableRoute implements IControllerRoute
$route = rtrim($this->url, '/') . '/{id?}/{action?}'; $route = rtrim($this->url, '/') . '/{id?}/{action?}';
/* Parse parameters from current route */ /* 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 no custom regular expression or parameters was found on this route, we stop */
if ($regexMatch === null && $this->parameters === null) { if ($regexMatch === null && $this->parameters === null) {
return false; return false;
} }
$action = strtolower(trim($this->parameters['action'])); $action = strtolower(trim((string)$this->parameters['action']));
$id = $this->parameters['id']; $id = $this->parameters['id'];
// Remove action parameter // Remove action parameter
@@ -172,12 +175,12 @@ class RouteResource extends LoadableRoute implements IControllerRoute
$this->name = $name; $this->name = $name;
$this->names = [ $this->names = [
'index' => $this->name . '.index', 'index' => $this->name . '.index',
'create' => $this->name . '.create', 'create' => $this->name . '.create',
'store' => $this->name . '.store', 'store' => $this->name . '.store',
'show' => $this->name . '.show', 'show' => $this->name . '.show',
'edit' => $this->name . '.edit', 'edit' => $this->name . '.edit',
'update' => $this->name . '.update', 'update' => $this->name . '.update',
'destroy' => $this->name . '.destroy', 'destroy' => $this->name . '.destroy',
]; ];
+7 -2
View File
@@ -6,7 +6,12 @@ use Pecee\Http\Request;
class RouteUrl extends LoadableRoute class RouteUrl extends LoadableRoute
{ {
public function __construct($url, $callback) /**
* RouteUrl constructor.
* @param string $url
* @param \Closure|string $callback
*/
public function __construct(string $url, $callback)
{ {
$this->setUrl($url); $this->setUrl($url);
$this->setCallback($callback); $this->setCallback($callback);
@@ -26,7 +31,7 @@ class RouteUrl extends LoadableRoute
} }
/* Parse parameters from current route */ /* 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 no custom regular expression or parameters was found on this route, we stop */
if ($regexMatch === null && $parameters === null) { if ($regexMatch === null && $parameters === null) {
+89 -60
View File
@@ -28,50 +28,56 @@ class Router
* Current request * Current request
* @var Request * @var Request
*/ */
protected $request; protected Request $request;
/** /**
* Defines if a route is currently being processed. * Defines if a route is currently being processed.
* @var bool * @var bool
*/ */
protected $isProcessingRoute; protected bool $isProcessingRoute;
/**
* Defines all data from current processing route.
* @var ILoadableRoute
*/
protected ILoadableRoute $currentProcessingRoute;
/** /**
* All added routes * All added routes
* @var array * @var array
*/ */
protected $routes = []; protected array $routes = [];
/** /**
* List of processed routes * List of processed routes
* @var array * @var array|ILoadableRoute[]
*/ */
protected $processedRoutes = []; protected array $processedRoutes = [];
/** /**
* Stack of routes used to keep track of sub-routes added * Stack of routes used to keep track of sub-routes added
* when a route is being processed. * when a route is being processed.
* @var array * @var array
*/ */
protected $routeStack = []; protected array $routeStack = [];
/** /**
* List of added bootmanagers * List of added bootmanagers
* @var array * @var array
*/ */
protected $bootManagers = []; protected array $bootManagers = [];
/** /**
* Csrf verifier class * Csrf verifier class
* @var BaseCsrfVerifier * @var BaseCsrfVerifier|null
*/ */
protected $csrfVerifier; protected ?BaseCsrfVerifier $csrfVerifier;
/** /**
* Get exception handlers * Get exception handlers
* @var array * @var array
*/ */
protected $exceptionHandlers = []; protected array $exceptionHandlers = [];
/** /**
* List of loaded exception that has been loaded. * List of loaded exception that has been loaded.
@@ -79,44 +85,44 @@ class Router
* *
* @var array * @var array
*/ */
protected $loadedExceptionHandlers = []; protected array $loadedExceptionHandlers = [];
/** /**
* Enable or disabled debugging * Enable or disabled debugging
* @var bool * @var bool
*/ */
protected $debugEnabled = false; protected bool $debugEnabled = false;
/** /**
* The start time used when debugging is enabled * The start time used when debugging is enabled
* @var float * @var float
*/ */
protected $debugStartTime; protected float $debugStartTime;
/** /**
* List containing all debug messages * List containing all debug messages
* @var array * @var array
*/ */
protected $debugList = []; protected array $debugList = [];
/** /**
* Contains any registered event-handler. * Contains any registered event-handler.
* @var array * @var array
*/ */
protected $eventHandlers = []; protected array $eventHandlers = [];
/** /**
* Class loader instance * Class loader instance
* @var ClassLoader * @var IClassLoader
*/ */
protected $classLoader; protected IClassLoader $classLoader;
/** /**
* When enabled the router will render all routes that matches. * When enabled the router will render all routes that matches.
* When disabled the router will stop execution when first route is found. * When disabled the router will stop execution when first route is found.
* @var bool * @var bool
*/ */
protected $renderMultipleRoutes = true; protected bool $renderMultipleRoutes = false;
/** /**
* Router constructor. * Router constructor.
@@ -203,7 +209,7 @@ class Router
/** /**
* Process added routes. * Process added routes.
* *
* @param array $routes * @param array|IRoute[] $routes
* @param IGroupRoute|null $group * @param IGroupRoute|null $group
* @throws NotFoundHttpException * @throws NotFoundHttpException
*/ */
@@ -211,11 +217,8 @@ class Router
{ {
$this->debug('Processing routes'); $this->debug('Processing routes');
// Loop through each route-request
$exceptionHandlers = [];
// Stop processing routes if no valid route is found. // Stop processing routes if no valid route is found.
if ($this->request->getRewriteRoute() === null && $this->request->getUrl() === null) { if ($this->request->getRewriteRoute() === null && $this->request->getUrl()->getOriginalUrl() === '') {
$this->debug('Halted route-processing as no valid route was found'); $this->debug('Halted route-processing as no valid route was found');
return; return;
@@ -223,7 +226,7 @@ class Router
$url = $this->request->getRewriteUrl() ?? $this->request->getUrl()->getPath(); $url = $this->request->getRewriteUrl() ?? $this->request->getUrl()->getPath();
/* @var $route IRoute */ // Loop through each route-request
foreach ($routes as $route) { foreach ($routes as $route) {
$this->debug('Processing route "%s"', get_class($route)); $this->debug('Processing route "%s"', get_class($route));
@@ -240,13 +243,22 @@ class Router
/* Add exception handlers */ /* Add exception handlers */
if (count($route->getExceptionHandlers()) !== 0) { 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 */ /* Only render partial group if it matches */
if ($route instanceof IPartialGroupRoute === true) { if ($route instanceof IPartialGroupRoute === true) {
$this->renderAndProcess($route); $this->renderAndProcess($route);
continue;
} }
} }
@@ -264,8 +276,6 @@ class Router
$this->processedRoutes[] = $route; $this->processedRoutes[] = $route;
} }
} }
$this->exceptionHandlers = array_merge($exceptionHandlers, $this->exceptionHandlers);
} }
/** /**
@@ -297,7 +307,7 @@ class Router
$this->debug('Rendering bootmanager "%s"', $className); $this->debug('Rendering bootmanager "%s"', $className);
$this->fireEvents(EventHandler::EVENT_RENDER_BOOTMANAGER, [ $this->fireEvents(EventHandler::EVENT_RENDER_BOOTMANAGER, [
'bootmanagers' => $this->bootManagers, 'bootmanagers' => $this->bootManagers,
'bootmanager' => $manager, 'bootmanager' => $manager,
]); ]);
/* Render bootmanager */ /* Render bootmanager */
@@ -332,8 +342,12 @@ class Router
'csrfVerifier' => $this->csrfVerifier, 'csrfVerifier' => $this->csrfVerifier,
]); ]);
/* Verify csrf token for request */ try {
$this->csrfVerifier->handle($this->request); /* Verify csrf token for request */
$this->csrfVerifier->handle($this->request);
} catch (Exception $e) {
return $this->handleException($e);
}
} }
$output = $this->routeRequest(); $output = $this->routeRequest();
@@ -368,6 +382,9 @@ class Router
$this->debug('Matching route "%s"', get_class($route)); $this->debug('Matching route "%s"', get_class($route));
/* Add current processing route to constants */
$this->currentProcessingRoute = $route;
/* If the route matches */ /* If the route matches */
if ($route->matchRoute($url, $this->request) === true) { if ($route->matchRoute($url, $this->request) === true) {
@@ -388,7 +405,7 @@ class Router
} }
$this->fireEvents(EventHandler::EVENT_RENDER_MIDDLEWARES, [ $this->fireEvents(EventHandler::EVENT_RENDER_MIDDLEWARES, [
'route' => $route, 'route' => $route,
'middlewares' => $route->getMiddlewares(), 'middlewares' => $route->getMiddlewares(),
]); ]);
@@ -410,7 +427,7 @@ class Router
$routeOutput = $route->renderRoute($this->request, $this); $routeOutput = $route->renderRoute($this->request, $this);
if ($this->renderMultipleRoutes === true) { if ($this->renderMultipleRoutes === true) {
if ($routeOutput !== null) { if ($routeOutput !== '') {
return $routeOutput; return $routeOutput;
} }
@@ -427,12 +444,12 @@ class Router
} }
} catch (Exception $e) { } catch (Exception $e) {
$this->handleException($e); return $this->handleException($e);
} }
if ($methodNotAllowed === true) { if ($methodNotAllowed === true) {
$message = sprintf('Route "%s" or method "%s" not allowed.', $this->request->getUrl()->getPath(), $this->request->getMethod()); $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) { if (count($this->request->getLoadedRoutes()) === 0) {
@@ -483,7 +500,7 @@ class Router
$this->request->setHasPendingRewrite(false); $this->request->setHasPendingRewrite(false);
$this->fireEvents(EventHandler::EVENT_REWRITE, [ $this->fireEvents(EventHandler::EVENT_REWRITE, [
'rewriteUrl' => $this->request->getRewriteUrl(), 'rewriteUrl' => $this->request->getRewriteUrl(),
'rewriteRoute' => $this->request->getRewriteRoute(), 'rewriteRoute' => $this->request->getRewriteRoute(),
]); ]);
@@ -504,7 +521,7 @@ class Router
$this->debug('Starting exception handling for "%s"', get_class($e)); $this->debug('Starting exception handling for "%s"', get_class($e));
$this->fireEvents(EventHandler::EVENT_LOAD_EXCEPTIONS, [ $this->fireEvents(EventHandler::EVENT_LOAD_EXCEPTIONS, [
'exception' => $e, 'exception' => $e,
'exceptionHandlers' => $this->exceptionHandlers, 'exceptionHandlers' => $this->exceptionHandlers,
]); ]);
@@ -516,8 +533,8 @@ class Router
} }
$this->fireEvents(EventHandler::EVENT_RENDER_EXCEPTION, [ $this->fireEvents(EventHandler::EVENT_RENDER_EXCEPTION, [
'exception' => $e, 'exception' => $e,
'exceptionHandler' => $handler, 'exceptionHandler' => $handler,
'exceptionHandlers' => $this->exceptionHandlers, 'exceptionHandlers' => $this->exceptionHandlers,
]); ]);
@@ -539,7 +556,7 @@ class Router
$this->debug('Exception handler contains rewrite, reloading routes'); $this->debug('Exception handler contains rewrite, reloading routes');
$this->fireEvents(EventHandler::EVENT_REWRITE, [ $this->fireEvents(EventHandler::EVENT_REWRITE, [
'rewriteUrl' => $this->request->getRewriteUrl(), 'rewriteUrl' => $this->request->getRewriteUrl(),
'rewriteRoute' => $this->request->getRewriteRoute(), 'rewriteRoute' => $this->request->getRewriteRoute(),
]); ]);
@@ -575,7 +592,6 @@ class Router
'name' => $name, 'name' => $name,
]); ]);
/* @var $route ILoadableRoute */
foreach ($this->processedRoutes as $route) { foreach ($this->processedRoutes as $route) {
/* Check if the name matches with a name on the route. Should match either router alias or controller alias. */ /* Check if the name matches with a name on the route. Should match either router alias or controller alias. */
@@ -593,10 +609,10 @@ class Router
} }
/* Using @ is most definitely a controller@method or alias@method */ /* Using @ is most definitely a controller@method or alias@method */
if (is_string($name) === true && strpos($name, '@') !== false) { if (strpos($name, '@') !== false) {
[$controller, $method] = array_map('strtolower', explode('@', $name)); [$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); $this->debug('Found route "%s" by controller "%s" and method "%s"', $route->getUrl(), $controller, $method);
return $route; return $route;
@@ -605,7 +621,7 @@ class Router
/* Check if callback matches (if it's not a function) */ /* Check if callback matches (if it's not a function) */
$callback = $route->getCallback(); $callback = $route->getCallback();
if (is_string($name) === true && is_string($callback) === true && is_callable($callback) === false && strpos($name, '@') !== false && strpos($callback, '@') !== false) { if (is_string($callback) === true && is_callable($callback) === false && strpos($name, '@') !== false && strpos($callback, '@') !== false) {
/* Check if the entire callback is matching */ /* Check if the entire callback is matching */
if (strpos($callback, $name) === 0 || strtolower($callback) === strtolower($name)) { if (strpos($callback, $name) === 0 || strtolower($callback) === strtolower($name)) {
@@ -651,15 +667,11 @@ class Router
$this->debug('Finding url', func_get_args()); $this->debug('Finding url', func_get_args());
$this->fireEvents(EventHandler::EVENT_GET_URL, [ $this->fireEvents(EventHandler::EVENT_GET_URL, [
'name' => $name, 'name' => $name,
'parameters' => $parameters, 'parameters' => $parameters,
'getParams' => $getParams, 'getParams' => $getParams,
]); ]);
if ($getParams !== null && is_array($getParams) === false) {
throw new InvalidArgumentException('Invalid type for getParams. Must be array or null');
}
if ($name === '' && $parameters === '') { if ($name === '' && $parameters === '') {
return new Url('/'); return new Url('/');
} }
@@ -684,7 +696,7 @@ class Router
->setParams($getParams); ->setParams($getParams);
} }
if($name !== null) { if ($name !== null) {
/* We try to find a match on the given name */ /* We try to find a match on the given name */
$route = $this->findRoute($name); $route = $this->findRoute($name);
@@ -703,21 +715,21 @@ class Router
/* Loop through all the routes to see if we can find a match */ /* Loop through all the routes to see if we can find a match */
/* @var $route ILoadableRoute */ /* @var $route ILoadableRoute */
foreach ($this->processedRoutes as $route) { foreach ($this->processedRoutes as $processedRoute) {
/* Check if the route contains the name/alias */ /* Check if the route contains the name/alias */
if ($route->hasName($controller) === true) { if ($processedRoute->hasName($controller) === true) {
return $this->request return $this->request
->getUrlCopy() ->getUrlCopy()
->setPath($route->findUrl($method, $parameters, $name)) ->setPath($processedRoute->findUrl($method, $parameters, $name))
->setParams($getParams); ->setParams($getParams);
} }
/* Check if the route controller is equal to the name */ /* Check if the route controller is equal to the name */
if ($route instanceof IControllerRoute && strtolower($route->getController()) === strtolower($controller)) { if ($processedRoute instanceof IControllerRoute && strtolower($processedRoute->getController()) === strtolower($controller)) {
return $this->request return $this->request
->getUrlCopy() ->getUrlCopy()
->setPath($route->findUrl($method, $parameters, $name)) ->setPath($processedRoute->findUrl($method, $parameters, $name))
->setParams($getParams); ->setParams($getParams);
} }
@@ -842,7 +854,7 @@ class Router
/** /**
* Get class loader * Get class loader
* *
* @return ClassLoader * @return IClassLoader
*/ */
public function getClassLoader(): IClassLoader public function getClassLoader(): IClassLoader
{ {
@@ -901,8 +913,8 @@ class Router
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2); $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
$this->debugList[] = [ $this->debugList[] = [
'message' => vsprintf($message, $args), 'message' => vsprintf($message, $args),
'time' => number_format(microtime(true) - $this->debugStartTime, 10), 'time' => number_format(microtime(true) - $this->debugStartTime, 10),
'trace' => end($trace), 'trace' => end($trace),
]; ];
} }
@@ -929,6 +941,16 @@ class Router
return $this->debugList; 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. * Changes the rendering behavior of the router.
* When enabled the router will render all routes that matches. * When enabled the router will render all routes that matches.
@@ -944,4 +966,11 @@ class Router
return $this; return $this;
} }
} public function addExceptionHandler(IExceptionHandler $handler): self
{
$this->exceptionHandlers[] = $handler;
return $this;
}
}
+19 -28
View File
@@ -22,6 +22,7 @@ use Pecee\SimpleRouter\Exceptions\HttpException;
use Pecee\SimpleRouter\Handlers\CallbackExceptionHandler; use Pecee\SimpleRouter\Handlers\CallbackExceptionHandler;
use Pecee\SimpleRouter\Handlers\IEventHandler; use Pecee\SimpleRouter\Handlers\IEventHandler;
use Pecee\SimpleRouter\Route\IGroupRoute; use Pecee\SimpleRouter\Route\IGroupRoute;
use Pecee\SimpleRouter\Route\ILoadableRoute;
use Pecee\SimpleRouter\Route\IPartialGroupRoute; use Pecee\SimpleRouter\Route\IPartialGroupRoute;
use Pecee\SimpleRouter\Route\IRoute; use Pecee\SimpleRouter\Route\IRoute;
use Pecee\SimpleRouter\Route\RouteController; use Pecee\SimpleRouter\Route\RouteController;
@@ -36,19 +37,19 @@ class SimpleRouter
* Default namespace added to all routes * Default namespace added to all routes
* @var string|null * @var string|null
*/ */
protected static $defaultNamespace; protected static ?string $defaultNamespace = null;
/** /**
* The response object * The response object
* @var Response * @var Response|null
*/ */
protected static $response; protected static ?Response $response = null;
/** /**
* Router instance * Router instance
* @var Router * @var Router
*/ */
protected static $router; protected static ?Router $router = null;
/** /**
* Start routing * Start routing
@@ -173,7 +174,7 @@ class SimpleRouter
*/ */
public static function redirect(string $where, string $to, int $httpCode = 301): IRoute public static function redirect(string $where, string $to, int $httpCode = 301): IRoute
{ {
return static::get($where, function () use ($to, $httpCode) { return static::get($where, static function () use ($to, $httpCode): void {
static::response()->redirect($to, $httpCode); static::response()->redirect($to, $httpCode);
}); });
} }
@@ -185,7 +186,7 @@ class SimpleRouter
* @param string|array|Closure $callback * @param string|array|Closure $callback
* @param array|null $settings * @param array|null $settings
* *
* @return RouteUrl * @return RouteUrl|IRoute
*/ */
public static function get(string $url, $callback, array $settings = null): IRoute public static function get(string $url, $callback, array $settings = null): IRoute
{ {
@@ -198,7 +199,7 @@ class SimpleRouter
* @param string $url * @param string $url
* @param string|array|Closure $callback * @param string|array|Closure $callback
* @param array|null $settings * @param array|null $settings
* @return RouteUrl * @return RouteUrl|IRoute
*/ */
public static function post(string $url, $callback, array $settings = null): IRoute public static function post(string $url, $callback, array $settings = null): IRoute
{ {
@@ -211,7 +212,7 @@ class SimpleRouter
* @param string $url * @param string $url
* @param string|array|Closure $callback * @param string|array|Closure $callback
* @param array|null $settings * @param array|null $settings
* @return RouteUrl * @return RouteUrl|IRoute
*/ */
public static function put(string $url, $callback, array $settings = null): IRoute public static function put(string $url, $callback, array $settings = null): IRoute
{ {
@@ -224,7 +225,7 @@ class SimpleRouter
* @param string $url * @param string $url
* @param string|array|Closure $callback * @param string|array|Closure $callback
* @param array|null $settings * @param array|null $settings
* @return RouteUrl * @return RouteUrl|IRoute
*/ */
public static function patch(string $url, $callback, array $settings = null): IRoute public static function patch(string $url, $callback, array $settings = null): IRoute
{ {
@@ -237,7 +238,7 @@ class SimpleRouter
* @param string $url * @param string $url
* @param string|array|Closure $callback * @param string|array|Closure $callback
* @param array|null $settings * @param array|null $settings
* @return RouteUrl * @return RouteUrl|IRoute
*/ */
public static function options(string $url, $callback, array $settings = null): IRoute public static function options(string $url, $callback, array $settings = null): IRoute
{ {
@@ -250,7 +251,7 @@ class SimpleRouter
* @param string $url * @param string $url
* @param string|array|Closure $callback * @param string|array|Closure $callback
* @param array|null $settings * @param array|null $settings
* @return RouteUrl * @return RouteUrl|IRoute
*/ */
public static function delete(string $url, $callback, array $settings = null): IRoute public static function delete(string $url, $callback, array $settings = null): IRoute
{ {
@@ -262,15 +263,11 @@ class SimpleRouter
* *
* @param array $settings * @param array $settings
* @param Closure $callback * @param Closure $callback
* @return RouteGroup * @return RouteGroup|IGroupRoute
* @throws InvalidArgumentException * @throws InvalidArgumentException
*/ */
public static function group(array $settings, Closure $callback): IGroupRoute public static function group(array $settings, Closure $callback): IGroupRoute
{ {
if (is_callable($callback) === false) {
throw new InvalidArgumentException('Invalid callback provided. Only functions or methods supported');
}
$group = new RouteGroup(); $group = new RouteGroup();
$group->setCallback($callback); $group->setCallback($callback);
$group->setSettings($settings); $group->setSettings($settings);
@@ -287,15 +284,11 @@ class SimpleRouter
* @param string $url * @param string $url
* @param Closure $callback * @param Closure $callback
* @param array $settings * @param array $settings
* @return RoutePartialGroup * @return RoutePartialGroup|IPartialGroupRoute
* @throws InvalidArgumentException * @throws InvalidArgumentException
*/ */
public static function partialGroup(string $url, Closure $callback, array $settings = []): IPartialGroupRoute public static function partialGroup(string $url, Closure $callback, array $settings = []): IPartialGroupRoute
{ {
if (is_callable($callback) === false) {
throw new InvalidArgumentException('Invalid callback provided. Only functions or methods supported');
}
$settings['prefix'] = $url; $settings['prefix'] = $url;
$group = new RoutePartialGroup(); $group = new RoutePartialGroup();
@@ -313,7 +306,7 @@ class SimpleRouter
* @param string $url * @param string $url
* @param string|array|Closure $callback * @param string|array|Closure $callback
* @param array|null $settings * @param array|null $settings
* @return RouteUrl * @return RouteUrl|IRoute
* @see SimpleRouter::form * @see SimpleRouter::form
*/ */
public static function basic(string $url, $callback, array $settings = null): IRoute public static function basic(string $url, $callback, array $settings = null): IRoute
@@ -328,7 +321,7 @@ class SimpleRouter
* @param string $url * @param string $url
* @param string|array|Closure $callback * @param string|array|Closure $callback
* @param array|null $settings * @param array|null $settings
* @return RouteUrl * @return RouteUrl|IRoute
* @see SimpleRouter::form * @see SimpleRouter::form
*/ */
public static function form(string $url, $callback, array $settings = null): IRoute public static function form(string $url, $callback, array $settings = null): IRoute
@@ -427,9 +420,7 @@ class SimpleRouter
{ {
$callbackHandler = new CallbackExceptionHandler($callback); $callbackHandler = new CallbackExceptionHandler($callback);
static::router()->addRoute( static::router()->addExceptionHandler($callbackHandler);
(new RouteGroup())->addExceptionHandler($callbackHandler)
);
return $callbackHandler; return $callbackHandler;
} }
@@ -501,8 +492,8 @@ class SimpleRouter
/** /**
* Prepends the default namespace to all new routes added. * Prepends the default namespace to all new routes added.
* *
* @param IRoute $route * @param ILoadableRoute|IRoute $route
* @return IRoute * @return IRoute|ILoadableRoute
*/ */
public static function addDefaultNamespace(IRoute $route): IRoute public static function addDefaultNamespace(IRoute $route): IRoute
{ {
@@ -2,12 +2,12 @@
class DummyCsrfVerifier extends \Pecee\Http\Middleware\BaseCsrfVerifier { class DummyCsrfVerifier extends \Pecee\Http\Middleware\BaseCsrfVerifier {
protected $except = [ protected array $except = [
'/exclude-page', '/exclude-page',
'/exclude-all/*', '/exclude-all/*',
]; ];
protected $include = [ protected array $include = [
'/exclude-all/include-page', '/exclude-all/include-page',
]; ];
@@ -2,12 +2,12 @@
class IpRestrictMiddleware extends \Pecee\Http\Middleware\IpRestrictAccess { class IpRestrictMiddleware extends \Pecee\Http\Middleware\IpRestrictAccess {
protected $ipBlacklist = [ protected array $ipBlacklist = [
'5.5.5.5', '5.5.5.5',
'8.8.*', '8.8.*',
]; ];
protected $ipWhitelist = [ protected array $ipWhitelist = [
'8.8.2.2', '8.8.2.2',
]; ];
@@ -0,0 +1,11 @@
<?php
namespace MyNamespace;
class NSController {
public function method()
{
return true;
}
}
@@ -66,4 +66,20 @@ class RouterResourceTest extends \PHPUnit\Framework\TestCase
} }
public function testResourceUrls()
{
TestRouter::resource('/resource', 'ResourceController')->name('resource');
TestRouter::debugOutputNoReset('/resource');
$this->assertEquals('/resource/3/create/', TestRouter::router()->getUrl('resource.create', ['id' => 3]));
$this->assertEquals('/resource/3/edit/', TestRouter::router()->getUrl('resource.edit', ['id' => 3]));
$this->assertEquals('/resource/3/', TestRouter::router()->getUrl('resource.update', ['id' => 3]));
$this->assertEquals('/resource/3/', TestRouter::router()->getUrl('resource.destroy', ['id' => 3]));
$this->assertEquals('/resource/3/', TestRouter::router()->getUrl('resource.delete', ['id' => 3]));
$this->assertEquals('/resource/', TestRouter::router()->getUrl('resource'));
TestRouter::router()->reset();
}
} }
+30 -3
View File
@@ -33,9 +33,9 @@ class RouterRewriteTest extends \PHPUnit\Framework\TestCase
global $stack; global $stack;
$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'); TestRouter::get('/my-path', 'DummyController@method1');
@@ -43,7 +43,7 @@ class RouterRewriteTest extends \PHPUnit\Framework\TestCase
}); });
try { try {
TestRouter::debug('/my-non-existing-path', 'get'); TestRouter::debug('/test/non-existing', 'get');
} catch (\ResponseException $e) { } 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() public function testRewriteExceptionMessage()
{ {
$this->expectException(\Pecee\SimpleRouter\Exceptions\NotFoundHttpException::class); $this->expectException(\Pecee\SimpleRouter\Exceptions\NotFoundHttpException::class);
@@ -2,6 +2,7 @@
require_once 'Dummy/DummyMiddleware.php'; require_once 'Dummy/DummyMiddleware.php';
require_once 'Dummy/DummyController.php'; require_once 'Dummy/DummyController.php';
require_once 'Dummy/NSController.php';
require_once 'Dummy/Exception/ExceptionHandlerException.php'; require_once 'Dummy/Exception/ExceptionHandlerException.php';
class RouterRouteTest extends \PHPUnit\Framework\TestCase class RouterRouteTest extends \PHPUnit\Framework\TestCase
@@ -174,6 +175,70 @@ class RouterRouteTest extends \PHPUnit\Framework\TestCase
TestRouter::debug('/test', 'get'); 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); $this->assertFalse($result);
} }
@@ -246,6 +311,16 @@ class RouterRouteTest extends \PHPUnit\Framework\TestCase
$this->assertTrue(true); $this->assertTrue(true);
} }
public function testDefaultNameSpaceOverload()
{
TestRouter::setDefaultNamespace('DefaultNamespace\\Controllers');
TestRouter::get('/test', [\MyNamespace\NSController::class, 'method']);
$result = TestRouter::debugOutput('/test');
$this->assertTrue( (bool)$result);
}
public function testSameRoutes() public function testSameRoutes()
{ {
TestRouter::get('/recipe', 'DummyController@method1')->name('add'); TestRouter::get('/recipe', 'DummyController@method1')->name('add');
+53 -17
View File
@@ -27,6 +27,23 @@ class RouterUrlTest extends \PHPUnit\Framework\TestCase
TestRouter::router()->reset(); 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() public function testUnicodeCharacters()
{ {
// Test spanish characters // Test spanish characters
@@ -181,6 +198,22 @@ class RouterUrlTest extends \PHPUnit\Framework\TestCase
$output = TestRouter::debugOutput('/admin/asd/bec/123', 'get'); $output = TestRouter::debugOutput('/admin/asd/bec/123', 'get');
$this->assertEquals('match', $output); $this->assertEquals('match', $output);
TestRouter::router()->reset();
}
public function testCustomRegexWithParameter()
{
TestRouter::request()->setHost('google.com');
$results = '';
TestRouter::get('/tester/{param}', function ($param = null) use ($results) {
return $results = $param;
})->setMatch('/(.*)/i');
$output = TestRouter::debugOutput('/tester/abepik/ko');
$this->assertEquals('/tester/abepik/ko/', $output);
} }
public function testRenderMultipleRoutesDisabled() public function testRenderMultipleRoutesDisabled()
@@ -218,9 +251,9 @@ class RouterUrlTest extends \PHPUnit\Framework\TestCase
TestRouter::debug('/'); TestRouter::debug('/');
$this->assertCount(2, $result); $this->assertCount(2, $result);
} }
public function testDefaultNamespace() public function testDefaultNamespace()
{ {
TestRouter::setDefaultNamespace('\\Default\\Namespace'); TestRouter::setDefaultNamespace('\\Default\\Namespace');
@@ -229,14 +262,14 @@ class RouterUrlTest extends \PHPUnit\Framework\TestCase
TestRouter::group([ TestRouter::group([
'namespace' => 'Appended\Namespace', 'namespace' => 'Appended\Namespace',
'prefix' => '/horses', 'prefix' => '/horses',
], function () { ], function () {
TestRouter::get('/', 'DummyController@method1'); TestRouter::get('/', 'DummyController@method1');
TestRouter::group([ TestRouter::group([
'namespace' => '\\New\\Namespace', 'namespace' => '\\New\\Namespace',
'prefix' => '/race', 'prefix' => '/race',
], function () { ], function () {
TestRouter::get('/', 'DummyController@method1'); TestRouter::get('/', 'DummyController@method1');
@@ -271,13 +304,14 @@ class RouterUrlTest extends \PHPUnit\Framework\TestCase
TestRouter::router()->reset(); TestRouter::router()->reset();
} }
public function testGroupPrefix() { public function testGroupPrefix()
{
$result = false; $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; $result = true;
}); });
}); });
@@ -291,13 +325,13 @@ class RouterUrlTest extends \PHPUnit\Framework\TestCase
$result = null; $result = null;
$expectedResult = 28; $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; $result = false;
}); });
TestRouter::get('/user/{userId}', function($userId) use(&$result) { TestRouter::get('/user/{userId}', function ($userId) use (&$result) {
$result = $userId; $result = $userId;
}); });
}); });
@@ -308,14 +342,15 @@ class RouterUrlTest extends \PHPUnit\Framework\TestCase
} }
public function testPassParameter() { public function testPassParameter()
{
$result = false; $result = false;
$expectedLanguage = 'da'; $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; $result = $language;
}); });
@@ -327,15 +362,16 @@ class RouterUrlTest extends \PHPUnit\Framework\TestCase
} }
public function testPassParameterDeep() { public function testPassParameterDeep()
{
$result = false; $result = false;
$expectedLanguage = 'da'; $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::group(['prefix' => '/admin'], function ($language) use (&$result) {
TestRouter::get('/test', function($language) use(&$result) { TestRouter::get('/test', function ($language) use (&$result) {
$result = $language; $result = $language;
}); });
}); });
+20 -5
View File
@@ -8,7 +8,7 @@ class TestRouter extends \Pecee\SimpleRouter\SimpleRouter
static::request()->setHost('testhost.com'); static::request()->setHost('testhost.com');
} }
public static function debugNoReset($testUrl, $testMethod = 'get') public static function debugNoReset(string $testUrl, string $testMethod = 'get'): void
{ {
$request = static::request(); $request = static::request();
@@ -18,22 +18,24 @@ class TestRouter extends \Pecee\SimpleRouter\SimpleRouter
static::start(); static::start();
} }
public static function debug($testUrl, $testMethod = 'get', bool $reset = true) public static function debug(string $testUrl, string $testMethod = 'get', bool $reset = true): void
{ {
try { try {
static::debugNoReset($testUrl, $testMethod); static::debugNoReset($testUrl, $testMethod);
} catch(\Exception $e) { } catch (\Exception $e) {
static::$defaultNamespace = null;
static::router()->reset(); static::router()->reset();
throw $e; throw $e;
} }
if($reset === true) { if ($reset === true) {
static::$defaultNamespace = null;
static::router()->reset(); static::router()->reset();
} }
} }
public static function debugOutput($testUrl, $testMethod = 'get', bool $reset = true) public static function debugOutput(string $testUrl, string $testMethod = 'get', bool $reset = true): string
{ {
$response = null; $response = null;
@@ -46,4 +48,17 @@ class TestRouter extends \Pecee\SimpleRouter\SimpleRouter
return $response; 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;
}
} }