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
- windows-latest
php-version:
- 7.1
- 7.4
- 8.0
phpunit-version:
- 7.5.20
- 8.5.32
dependencies:
- lowest
- highest
+2 -1
View File
@@ -1,4 +1,5 @@
composer.lock
vendor/
.idea/
.phpunit.result.cache
.phpunit.result.cache
tests/tmp
+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)
- [Multiple HTTP-verbs](#multiple-http-verbs)
- [Route parameters](#route-parameters)
- [Required parameters](#required-parameters)
- [Optional parameters](#optional-parameters)
- [Regular expression constraints](#regular-expression-constraints)
- [Regular expression route-match](#regular-expression-route-match)
- [Custom regex for matching parameters](#custom-regex-for-matching-parameters)
- [Required parameters](#required-parameters)
- [Optional parameters](#optional-parameters)
- [Including slash in parameters](#including-slash-in-parameters)
- [Regular expression constraints](#regular-expression-constraints)
- [Regular expression route-match](#regular-expression-route-match)
- [Custom regex for matching parameters](#custom-regex-for-matching-parameters)
- [Named routes](#named-routes)
- [Generating URLs To Named Routes](#generating-urls-to-named-routes)
- [Router groups](#router-groups)
@@ -62,6 +63,7 @@ You can donate any amount of your choice by [clicking here](https://www.paypal.c
- [ExceptionHandlers](#exceptionhandlers)
- [Handling 404, 403 and other errors](#handling-404-403-and-other-errors)
- [Using custom exception handlers](#using-custom-exception-handlers)
- [Prevent merge of parent exception-handlers](#prevent-merge-of-parent-exception-handlers)
- [Urls](#urls)
- [Get the current url](#get-the-current-url)
- [Get by name (single route)](#get-by-name-single-route)
@@ -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)
- [Managing files](#managing-files)
- [Get all parameters](#get-all-parameters)
- [Check if parameters exists](#check-if-parameters-exists)
- [Events](#events)
- [Available events](#available-events)
- [Registering new event](#registering-new-event)
- [Custom EventHandlers](#custom-eventhandlers)
- [Advanced](#advanced)
- [Disable multiple route rendering](#disable-multiple-route-rendering)
- [Multiple route rendering](#multiple-route-rendering)
- [Restrict access to IP](#restrict-access-to-ip)
- [Setting custom base path](#setting-custom-base-path)
- [Url rewriting](#url-rewriting)
@@ -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
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:
```php
Router::csrfVerifier(new \Demo\Middlewares\CsrfVerifier());
SimpleRouter::csrfVerifier(new \Demo\Middlewares\CsrfVerifier());
```
## Getting CSRF-token
@@ -806,7 +831,7 @@ csrf_token();
You can also get the token directly:
```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.
@@ -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:
```php
$verifier = new \dscuz\Middleware\CsrfVerifier();
$verifier = new \Demo\Middlewares\CsrfVerifier();
$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
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.
@@ -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.
```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) {
response()->redirect('/not-found');
switch($exception->getCode()) {
// 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
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;
}
/* 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;
@@ -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
@@ -1236,8 +1327,11 @@ $values = input()->all([
All object implements the `IInputItem` interface and will always contain these methods:
- `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).
- `setName()` - sets a human friendly name for the input (company_name will be Company Name etc).
- `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:
@@ -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
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_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_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_RENDER_ROUTE` | `route` | Fires before a route is rendered. |
| `EVENT_LOAD_EXCEPTIONS` | `exception`<br>`exceptionHandlers` | Fires when the router is loading exception-handlers. |
@@ -1391,11 +1503,12 @@ class DatabaseDebugHandler implements IEventHandler
# Advanced
## Disable multiple route rendering
## Multiple route rendering
By default the router will try to execute all routes that matches a given url. To stop the router from executing any further routes any method can return a value.
If you need multiple routes to be executed on the same url, you can enable this feature by setting `SimpleRouter::enableMultiRouteRendering(true)`
in your `routes.php` file.
This behavior can be easily disabled by setting `SimpleRouter::enableMultiRouteRendering(false)` in your `routes.php` file. This is the same behavior as version 3 and below.
This is most commonly used in advanced cases, for example in CMS systems where multiple routes needs to be rendered.
## Restrict access to IP
@@ -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.
@@ -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
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
SOFTWARE.
+13 -4
View File
@@ -27,12 +27,16 @@
}
],
"require": {
"php": ">=7.1",
"php": ">=7.4",
"ext-json": "*"
},
"require-dev": {
"phpunit/phpunit": "^7",
"mockery/mockery": "^1"
"phpunit/phpunit": "^8",
"mockery/mockery": "^1",
"phpstan/phpstan": "^1",
"phpstan/phpstan-phpunit": "^1",
"phpstan/phpstan-deprecation-rules": "^1",
"phpstan/phpstan-strict-rules": "^1"
},
"scripts": {
"test": [
@@ -43,5 +47,10 @@
"psr-4": {
"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;
/**
* @return mixed
*/
public function getValue();
/**
* @param mixed $value
*/
public function setValue($value): self;
public function __toString(): string;
+37 -10
View File
@@ -6,13 +6,40 @@ use Pecee\Exceptions\InvalidArgumentException;
class InputFile implements IInputItem
{
public $index;
public $name;
public $filename;
public $size;
public $type;
public $errors;
public $tmpName;
/**
* @var string
*/
public string $index;
/**
* @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)
{
@@ -47,7 +74,7 @@ class InputFile implements IInputItem
'error' => null,
];
return (new static($values['index']))
return (new self($values['index']))
->setSize((int)$values['size'])
->setError((int)$values['error'])
->setType($values['type'])
@@ -77,9 +104,9 @@ class InputFile implements IInputItem
}
/**
* @return string
* @return int
*/
public function getSize(): string
public function getSize(): ?int
{
return $this->size;
}
+24 -12
View File
@@ -10,40 +10,40 @@ class InputHandler
/**
* @var array
*/
protected $get = [];
protected array $get = [];
/**
* @var array
*/
protected $post = [];
protected array $post = [];
/**
* @var array
*/
protected $file = [];
protected array $file = [];
/**
* @var Request
*/
protected $request;
protected Request $request;
/**
* Original post variables
* @var array
*/
protected $originalPost = [];
protected array $originalPost = [];
/**
* Original get/params variables
* @var array
*/
protected $originalParams = [];
protected array $originalParams = [];
/**
* Get original file variables
* @var array
*/
protected $originalFile = [];
protected array $originalFile = [];
/**
* Input constructor.
@@ -115,7 +115,7 @@ class InputHandler
// Handle array input
if (is_array($value['name']) === false) {
$values['index'] = $parentKey ?? $key;
$values = ['index' => $parentKey ?? $key];
try {
$list[$key] = InputFile::createFromArray($values + $value);
@@ -161,7 +161,7 @@ class InputHandler
try {
$file = InputFile::createFromArray([
'index' => (empty($key) === true && empty($originalIndex) === false) ? $originalIndex : $key,
'index' => ($key === '' && $originalIndex !== '') ? $originalIndex : $key,
'name' => $original['name'][$key],
'error' => $original['error'][$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
* @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;
}
+13 -4
View File
@@ -8,10 +8,18 @@ use IteratorAggregate;
class InputItem implements ArrayAccess, IInputItem, IteratorAggregate
{
public $index;
public $name;
public string $index;
public string $name;
/**
* @var mixed|null
*/
public $value;
/**
* @param string $index
* @param mixed $value
*/
public function __construct(string $index, $value = null)
{
$this->index = $index;
@@ -81,7 +89,8 @@ class InputItem implements ArrayAccess, IInputItem, IteratorAggregate
return isset($this->value[$offset]);
}
public function offsetGet($offset)
#[\ReturnTypeWillChange]
public function offsetGet($offset): ?self
{
if ($this->offsetExists($offset) === true) {
return $this->value[$offset];
@@ -97,7 +106,7 @@ class InputItem implements ArrayAccess, IInputItem, IteratorAggregate
public function offsetUnset($offset): void
{
unset($this->data[$offset]);
unset($this->value[$offset]);
}
public function __toString(): string
+30 -20
View File
@@ -17,13 +17,18 @@ class BaseCsrfVerifier implements IMiddleware
* For example: /admin/*
* @var array|null
*/
protected $except;
protected array $except = [];
/**
* Urls to include. Can be used to include urls from a certain path.
* @var array|null
*/
protected $include;
protected $tokenProvider;
protected array $include = [];
/**
* @var ITokenProvider
*/
protected ITokenProvider $tokenProvider;
/**
* BaseCsrfVerifier constructor.
@@ -33,6 +38,23 @@ class BaseCsrfVerifier implements IMiddleware
$this->tokenProvider = new CookieTokenProvider();
}
protected function isIncluded(Request $request): bool
{
if (count($this->include) > 0) {
foreach ($this->include as $includeUrl) {
$includeUrl = rtrim($includeUrl, '/');
if ($includeUrl[strlen($includeUrl) - 1] === '*') {
$includeUrl = rtrim($includeUrl, '*');
return $request->getUrl()->contains($includeUrl);
}
return ($includeUrl === rtrim($request->getUrl()->getRelativeUrl(false), '/'));
}
}
return false;
}
/**
* Check if the url matches the urls in the except property
* @param Request $request
@@ -40,11 +62,11 @@ class BaseCsrfVerifier implements IMiddleware
*/
protected function skip(Request $request): bool
{
if ($this->except === null || count($this->except) === 0) {
if (count($this->except) === 0) {
return false;
}
foreach($this->except as $url) {
foreach ($this->except as $url) {
$url = rtrim($url, '/');
if ($url[strlen($url) - 1] === '*') {
$url = rtrim($url, '*');
@@ -55,20 +77,9 @@ class BaseCsrfVerifier implements IMiddleware
if ($skip === true) {
if(is_array($this->include) === true && count($this->include) > 0) {
foreach($this->include as $includeUrl) {
$includeUrl = rtrim($includeUrl, '/');
if ($includeUrl[strlen($includeUrl) - 1] === '*') {
$includeUrl = rtrim($includeUrl, '*');
$skip = !$request->getUrl()->contains($includeUrl);
break;
}
$skip = !$this->isIncluded($request);
$skip = !($includeUrl === rtrim($request->getUrl()->getRelativeUrl(false), '/'));
}
}
if($skip === false) {
if ($skip === false) {
continue;
}
@@ -87,12 +98,11 @@ class BaseCsrfVerifier implements IMiddleware
*/
public function handle(Request $request): void
{
if ($this->skip($request) === false && $request->isPostBack() === true) {
if ($this->skip($request) === false && ($request->isPostBack() === true || $this->isIncluded($request) === true)) {
$token = $request->getInputHandler()->value(
static::POST_KEY,
$request->getHeader(static::HEADER_KEY),
Request::$requestTypesPost
);
if ($this->tokenProvider->validate((string)$token) === false) {
@@ -7,8 +7,8 @@ use Pecee\SimpleRouter\Exceptions\HttpException;
abstract class IpRestrictAccess implements IMiddleware
{
protected $ipBlacklist = [];
protected $ipWhitelist = [];
protected array $ipBlacklist = [];
protected array $ipWhitelist = [];
protected function validate(string $ip): bool
{
+29 -22
View File
@@ -29,7 +29,7 @@ class Request
* All request-types
* @var string[]
*/
public static $requestTypes = [
public static array $requestTypes = [
self::REQUEST_TYPE_GET,
self::REQUEST_TYPE_POST,
self::REQUEST_TYPE_PUT,
@@ -43,7 +43,7 @@ class Request
* Post request-types.
* @var string[]
*/
public static $requestTypesPost = [
public static array $requestTypesPost = [
self::REQUEST_TYPE_POST,
self::REQUEST_TYPE_PUT,
self::REQUEST_TYPE_PATCH,
@@ -55,65 +55,65 @@ class Request
*
* @var array
*/
private $data = [];
private array $data = [];
/**
* Server headers
* @var array
*/
protected $headers = [];
protected array $headers = [];
/**
* Request ContentType
* @var string
*/
protected $contentType;
protected string $contentType;
/**
* Request host
* @var string
* @var string|null
*/
protected $host;
protected ?string $host;
/**
* Current request url
* @var Url
*/
protected $url;
protected Url $url;
/**
* Request method
* @var string
*/
protected $method;
protected string $method;
/**
* Input handler
* @var InputHandler
*/
protected $inputHandler;
protected InputHandler $inputHandler;
/**
* Defines if request has pending rewrite
* @var bool
*/
protected $hasPendingRewrite = false;
protected bool $hasPendingRewrite = false;
/**
* @var ILoadableRoute|null
*/
protected $rewriteRoute;
protected ?ILoadableRoute $rewriteRoute = null;
/**
* Rewrite url
* @var string|null
*/
protected $rewriteUrl;
protected ?string $rewriteUrl = null;
/**
* @var array
*/
protected $loadedRoutes = [];
protected array $loadedRoutes = [];
/**
* Request constructor.
@@ -129,7 +129,12 @@ class Request
$this->setHost($this->getHeader('http-host'));
// Check if special IIS header exist, otherwise use default.
$this->setUrl(new Url($this->getFirstHeader(['unencoded-url', 'request-uri'])));
$url = $this->getHeader('unencoded-url');
if($url !== null){
$this->setUrl(new Url($url));
}else{
$this->setUrl(new Url(urldecode((string)$this->getHeader('request-uri'))));
}
$this->setContentType((string)$this->getHeader('content-type'));
$this->setMethod((string)($_POST[static::FORCE_METHOD_KEY] ?? $this->getHeader('request-method')));
$this->inputHandler = new InputHandler($this);
@@ -137,7 +142,7 @@ class Request
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
{
$headers = ['remote-addr'];
$headers = [];
if($safeMode === false) {
$headers = array_merge($headers, [
$headers = [
'http-cf-connecting-ip',
'http-client-ip',
'http-x-forwarded-for',
]);
];
}
$headers[] = 'remote-addr';
return $this->getFirstHeader($headers);
}
@@ -264,12 +271,12 @@ class Request
* Get header value by name
*
* @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.
*
* @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);
$header = $this->headers[$name] ?? null;
@@ -359,7 +366,7 @@ class Request
*/
public function isAjax(): bool
{
return (strtolower($this->getHeader('http-x-requested-with')) === 'xmlhttprequest');
return (strtolower((string)$this->getHeader('http-x-requested-with')) === 'xmlhttprequest');
}
/**
+3 -4
View File
@@ -7,7 +7,7 @@ use Pecee\Exceptions\InvalidArgumentException;
class Response
{
protected $request;
protected Request $request;
public function __construct(Request $request)
{
@@ -65,7 +65,6 @@ class Response
public function cache(string $eTag, int $lastModifiedTime = 2592000): self
{
$this->headers([
'Cache-Control: public',
sprintf('Last-Modified: %s GMT', gmdate('D, d M Y H:i:s', $lastModifiedTime)),
@@ -87,11 +86,11 @@ class Response
/**
* Json encode
* @param array|JsonSerializable $value
* @param ?int $options JSON options Bitmask consisting of JSON_HEX_QUOT, JSON_HEX_TAG, JSON_HEX_AMP, JSON_HEX_APOS, JSON_NUMERIC_CHECK, JSON_PRETTY_PRINT, JSON_UNESCAPED_SLASHES, JSON_FORCE_OBJECT, JSON_PRESERVE_ZERO_FRACTION, JSON_UNESCAPED_UNICODE, JSON_PARTIAL_OUTPUT_ON_ERROR.
* @param int $options JSON options Bitmask consisting of JSON_HEX_QUOT, JSON_HEX_TAG, JSON_HEX_AMP, JSON_HEX_APOS, JSON_NUMERIC_CHECK, JSON_PRETTY_PRINT, JSON_UNESCAPED_SLASHES, JSON_FORCE_OBJECT, JSON_PRESERVE_ZERO_FRACTION, JSON_UNESCAPED_UNICODE, JSON_PARTIAL_OUTPUT_ON_ERROR.
* @param int $dept JSON debt.
* @throws InvalidArgumentException
*/
public function json($value, ?int $options = null, int $dept = 512): void
public function json($value, int $options = 0, int $dept = 512): void
{
if (($value instanceof JsonSerializable) === false && is_array($value) === false) {
throw new InvalidArgumentException('Invalid type for parameter "value". Must be of type array or object implementing the \JsonSerializable interface.');
@@ -9,8 +9,15 @@ class CookieTokenProvider implements ITokenProvider
{
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.
+67 -17
View File
@@ -7,16 +7,56 @@ use Pecee\Http\Exceptions\MalformedUrlException;
class Url implements JsonSerializable
{
private $originalUrl;
/**
* @var string|null
*/
private ?string $originalUrl = null;
private $scheme;
private $host;
private $port;
private $username;
private $password;
private $path;
private $params = [];
private $fragment;
/**
* @var string|null
*/
private ?string $scheme = null;
/**
* @var string|null
*/
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.
@@ -39,6 +79,7 @@ class Url implements JsonSerializable
if (isset($data['path']) === true) {
$this->setPath($data['path']);
$this->originalPath = $data['path'];
}
$this->fragment = $data['fragment'] ?? null;
@@ -192,6 +233,15 @@ class Url implements JsonSerializable
return $this->path ?? '/';
}
/**
* Get original path with no sanitization of ending trail/slash.
* @return string|null
*/
public function getOriginalPath(): ?string
{
return $this->originalPath;
}
/**
* Set the url path
*
@@ -250,7 +300,7 @@ class Url implements JsonSerializable
$params = [];
parse_str($queryString, $params);
if(count($params) > 0) {
if (count($params) > 0) {
return $this->setParams($params);
}
@@ -387,7 +437,7 @@ class Url implements JsonSerializable
{
$encodedUrl = preg_replace_callback(
'/[^:\/@?&=#]+/u',
static function ($matches) {
static function ($matches): string {
return urlencode($matches[0]);
},
$url
@@ -414,7 +464,7 @@ class Url implements JsonSerializable
if (count($getParams) !== 0) {
if ($includeEmpty === false) {
$getParams = array_filter($getParams, static function ($item) {
$getParams = array_filter($getParams, static function ($item): bool {
return (trim($item) !== '');
});
}
@@ -431,11 +481,11 @@ class Url implements JsonSerializable
* @param bool $includeParams
* @return string
*/
public function getRelativeUrl($includeParams = true): string
public function getRelativeUrl(bool $includeParams = true): string
{
$path = $this->path ?? '/';
if($includeParams === false) {
if ($includeParams === false) {
return $path;
}
@@ -451,14 +501,14 @@ class Url implements JsonSerializable
* @param bool $includeParams
* @return string
*/
public function getAbsoluteUrl($includeParams = true): string
public function getAbsoluteUrl(bool $includeParams = true): string
{
$scheme = $this->scheme !== null ? $this->scheme . '://' : '';
$host = $this->host ?? '';
$port = $this->port !== null ? ':' . $this->port : '';
$user = $this->username ?? '';
$pass = $this->password !== null ? ':' . $this->password : '';
$pass = ($user || $pass) ? $pass . '@' : '';
$pass = ($user !== '' || $pass !== '') ? $pass . '@' : '';
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
* @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.
* @since 5.4.0
*/
@@ -27,11 +27,11 @@ class ClassLoader implements IClassLoader
* @param object $class
* @param string $method
* @param array $parameters
* @return object
* @return string
*/
public function loadClassMethod($class, string $method, array $parameters)
public function loadClassMethod($class, string $method, array $parameters): string
{
return call_user_func_array([$class, $method], array_values($parameters));
return (string)call_user_func_array([$class, $method], array_values($parameters));
}
/**
@@ -39,11 +39,11 @@ class ClassLoader implements IClassLoader
*
* @param Callable $closure
* @param array $parameters
* @return mixed
* @return string
*/
public function loadClosure(Callable $closure, array $parameters)
public function loadClosure(callable $closure, array $parameters): string
{
return call_user_func_array($closure, array_values($parameters));
return (string)call_user_func_array($closure, array_values($parameters));
}
}
@@ -17,7 +17,7 @@ interface IClassLoader
* @param object $class
* @param string $method
* @param array $parameters
* @return object
* @return mixed
*/
public function loadClassMethod($class, string $method, array $parameters);
@@ -12,19 +12,19 @@ class EventArgument implements IEventArgument
* Event name
* @var string
*/
protected $eventName;
protected string $eventName;
/**
* @var Router
*/
protected $router;
protected Router $router;
/**
* @var array
*/
protected $arguments = [];
protected array $arguments = [];
public function __construct($eventName, $router, array $arguments = [])
public function __construct(string $eventName, Router $router, array $arguments = [])
{
$this->eventName = $eventName;
$this->router = $router;
@@ -94,7 +94,7 @@ class EventArgument implements IEventArgument
* @param mixed $value
* @throws InvalidArgumentException
*/
public function __set(string $name, $value)
public function __set(string $name, $value): void
{
throw new InvalidArgumentException('Not supported');
}
@@ -6,10 +6,17 @@ use Throwable;
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);
@@ -17,7 +17,10 @@ use Pecee\Http\Request;
class CallbackExceptionHandler implements IExceptionHandler
{
protected $callback;
/**
* @var Closure
*/
protected Closure $callback;
public function __construct(Closure $callback)
{
@@ -13,11 +13,11 @@ class DebugEventHandler implements IEventHandler
* Debug callback
* @var Closure
*/
protected $callback;
protected Closure $callback;
public function __construct()
{
$this->callback = static function (EventArgument $argument) {
$this->callback = static function (EventArgument $argument): void {
// todo: log in database
};
}
@@ -47,7 +47,7 @@ class DebugEventHandler implements IEventHandler
public function fireEvents(Router $router, string $name, array $eventArgs = []): void
{
$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
* @var array
*/
public static $events = [
public static array $events = [
self::EVENT_ALL,
self::EVENT_INIT,
self::EVENT_LOAD,
@@ -120,7 +120,7 @@ class EventHandler implements IEventHandler
* List of all registered events
* @var array
*/
private $registeredEvents = [];
private array $registeredEvents = [];
/**
* Register new event
@@ -2,7 +2,7 @@
namespace Pecee\SimpleRouter\Route;
interface IControllerRoute extends IRoute
interface IControllerRoute extends ILoadableRoute
{
/**
* Get controller class-name
@@ -31,6 +31,21 @@ interface IGroupRoute extends IRoute
*/
public function setExceptionHandlers(array $handlers): self;
/**
* Returns true if group should overwrite existing exception-handlers.
*
* @return bool
*/
public function getMergeExceptionHandlers(): bool;
/**
* When enabled group will overwrite any existing exception-handlers.
*
* @param bool $merge
* @return static
*/
public function setMergeExceptionHandlers(bool $merge): self;
/**
* Get exception-handlers for group
*
+29 -7
View File
@@ -12,14 +12,17 @@ abstract class LoadableRoute extends Route implements ILoadableRoute
/**
* @var string
*/
protected $url;
protected string $url;
/**
* @var string
*/
protected $name;
protected ?string $name = null;
protected $regex;
/**
* @var string|null
*/
protected ?string $regex = null;
/**
* Loads and renders middlewares-classes
@@ -59,7 +62,14 @@ abstract class LoadableRoute extends Route implements ILoadableRoute
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;
}
/**
* 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.
* 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) {
/* Add parameter to the correct position */
$url = str_ireplace([sprintf($param1, $param), sprintf($param2, $param)], $value, $url);
$url = str_ireplace([sprintf($param1, $param), sprintf($param2, $param)], (string)$value, $url);
} else {
/* Parameter aren't recognized and will be appended at the end of the url */
$url .= $value . '/';
@@ -173,7 +195,7 @@ abstract class LoadableRoute extends Route implements ILoadableRoute
*/
public function hasName(string $name): bool
{
return strtolower($this->name) === strtolower($name);
return strtolower((string)$this->name) === strtolower($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.
* Alias for LoadableRoute::setName().
*
* @see LoadableRoute::setName()
* @param string|array $name
* @return static
* @see LoadableRoute::setName()
*/
public function name($name): ILoadableRoute
{
+67 -22
View File
@@ -18,28 +18,37 @@ abstract class Route implements IRoute
*
* @var bool
*/
protected $filterEmptyParams = true;
protected bool $filterEmptyParams = true;
/**
* If true the last parameter of the route will include ending trail/slash.
* @var bool
*/
protected bool $slashParameterEnabled = false;
/**
* Default regular expression used for parsing parameters.
* @var string|null
*/
protected $defaultParameterRegex;
protected $paramModifiers = '{}';
protected $paramOptionalSymbol = '?';
protected $urlRegex = '/^%s\/?$/u';
protected $group;
protected $parent;
protected ?string $defaultParameterRegex = null;
protected string $paramModifiers = '{}';
protected string $paramOptionalSymbol = '?';
protected string $urlRegex = '/^%s\/?$/u';
protected ?IGroupRoute $group = null;
protected ?IRoute $parent = null;
/**
* @var string|callable|null
*/
protected $callback;
protected $defaultNamespace;
protected ?string $defaultNamespace = null;
/* Default options */
protected $namespace;
protected $requestMethods = [];
protected $where = [];
protected $parameters = [];
protected $originalParameters = [];
protected $middlewares = [];
protected ?string $namespace = null;
protected array $requestMethods = [];
protected array $where = [];
protected array $parameters = [];
protected array $originalParameters = [];
protected array $middlewares = [];
/**
* Render route
@@ -67,7 +76,7 @@ abstract class Route implements IRoute
/* Filter parameters with null-value */
if ($this->filterEmptyParams === true) {
$parameters = array_filter($parameters, static function ($var) {
$parameters = array_filter($parameters, static function ($var): bool {
return ($var !== null);
});
}
@@ -82,6 +91,7 @@ abstract class Route implements IRoute
}
/* When the callback is a function */
return $router->getClassLoader()->loadClosure($callback, $parameters);
}
@@ -107,7 +117,7 @@ abstract class Route implements IRoute
return $router->getClassLoader()->loadClassMethod($class, $method, $parameters);
}
protected function parseParameters($route, $url, $parameterRegex = null): ?array
protected function parseParameters($route, $url, Request $request, $parameterRegex = null): ?array
{
$regex = (strpos($route, $this->paramModifiers[0]) === false) ? null :
sprintf
@@ -119,7 +129,10 @@ abstract class Route implements IRoute
);
// Ensures that host names/domains will work with parameters
$url = '/' . ltrim($url, '/');
if ($route[0] === $this->paramModifiers[0]) {
$url = '/' . ltrim($url, '/');
}
$urlRegex = '';
$parameters = [];
@@ -127,7 +140,7 @@ abstract class Route implements IRoute
$urlRegex = preg_quote($route, '/');
} else {
foreach (preg_split('/((-?\/?){[^}]+})/', $route) as $key => $t) {
foreach (preg_split('/((\.?-?\/?){[^' . $this->paramModifiers[1] . ']+' . $this->paramModifiers[1] . ')/', $route) as $key => $t) {
$regex = '';
@@ -142,13 +155,14 @@ abstract class Route implements IRoute
$regex = $parameterRegex ?? $this->defaultParameterRegex ?? static::PARAMETERS_DEFAULT_REGEX;
}
$regex = sprintf('((\/|-)(?P<%2$s>%3$s))%1$s', $parameters[2][$key], $name, $regex);
$regex = sprintf('((\/|-|\.)(?P<%2$s>%3$s))%1$s', $parameters[2][$key], $name, $regex);
}
$urlRegex .= preg_quote($t, '/') . $regex;
}
}
// Get name of last param
if (trim($urlRegex) === '' || (bool)preg_match(sprintf($this->urlRegex, $urlRegex), $url, $matches) === false) {
return null;
}
@@ -162,7 +176,8 @@ abstract class Route implements IRoute
$lastParams = [];
/* Only take matched parameters with name */
foreach ((array)$parameters[1] as $name) {
$originalPath = $request->getUrl()->getOriginalPath();
foreach ((array)$parameters[1] as $i => $name) {
// Ignore parent parameters
if (isset($groupParameters[$name]) === true) {
@@ -170,10 +185,16 @@ abstract class Route implements IRoute
continue;
}
// If last parameter and slash parameter is enabled, use slash according to original path (non sanitized version)
$lastParameter = $this->paramModifiers[0] . $name . $this->paramModifiers[1] . '/';
if ($this->slashParameterEnabled && ($i === count($parameters[1]) - 1) && (substr_compare($route, $lastParameter, -strlen($lastParameter)) === 0) && $originalPath[strlen($originalPath) - 1] === '/') {
$matches[$name] .= '/';
}
$values[$name] = (isset($matches[$name]) === true && $matches[$name] !== '') ? $matches[$name] : null;
}
$values = array_merge($values, $lastParams);
$values += $lastParams;
}
$this->originalParameters = $values;
@@ -280,7 +301,7 @@ abstract class Route implements IRoute
}
/**
* @return string|callable
* @return string|callable|null
*/
public function getCallback()
{
@@ -337,6 +358,11 @@ abstract class Route implements 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();
if ($ns !== null) {
@@ -377,6 +403,17 @@ abstract class Route implements IRoute
return $this->namespace ?? $this->defaultNamespace;
}
public function setSlashParameterEnabled(bool $enabled): self
{
$this->slashParameterEnabled = $enabled;
return $this;
}
public function getSlashParameterEnabled(): bool
{
return $this->slashParameterEnabled;
}
/**
* Export route settings to array so they can be merged with another route.
*
@@ -406,6 +443,10 @@ abstract class Route implements IRoute
$values['defaultParameterRegex'] = $this->defaultParameterRegex;
}
if ($this->slashParameterEnabled === true) {
$values['includeSlash'] = $this->slashParameterEnabled;
}
return $values;
}
@@ -443,6 +484,10 @@ abstract class Route implements IRoute
$this->setDefaultParameterRegex($settings['defaultParameterRegex']);
}
if (isset($settings['includeSlash']) === true) {
$this->setSlashParameterEnabled($settings['includeSlash']);
}
return $this;
}
@@ -6,10 +6,10 @@ use Pecee\Http\Request;
class RouteController extends LoadableRoute implements IControllerRoute
{
protected $defaultMethod = 'index';
protected $controller;
protected $method;
protected $names = [];
protected string $defaultMethod = 'index';
protected string $controller;
protected ?string $method = null;
protected array $names = [];
public function __construct($url, $controller)
{
@@ -52,7 +52,7 @@ class RouteController extends LoadableRoute implements IControllerRoute
public function findUrl(?string $method = null, $parameters = null, ?string $name = null): string
{
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) {
$method = (string)$found;
}
@@ -67,7 +67,7 @@ class RouteController extends LoadableRoute implements IControllerRoute
foreach (Request::$requestTypes as $requestType) {
if (stripos($method, $requestType) === 0) {
$method = (string)substr($method, strlen($requestType));
$method = substr($method, strlen($requestType));
break;
}
}
@@ -81,14 +81,14 @@ class RouteController extends LoadableRoute implements IControllerRoute
$url .= '//' . $group->getDomains()[0];
}
$url .= '/' . trim($this->getUrl(), '/') . '/' . strtolower($method) . implode('/', $parameters);
$url .= '/' . trim($this->getUrl(), '/') . '/' . strtolower((string)$method) . implode('/', $parameters);
return '/' . trim($url, '/') . '/';
}
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;
}
+39 -10
View File
@@ -7,11 +7,12 @@ use Pecee\SimpleRouter\Handlers\IExceptionHandler;
class RouteGroup extends Route implements IGroupRoute
{
protected $urlRegex = '/^%s\/?/u';
protected $prefix;
protected $name;
protected $domains = [];
protected $exceptionHandlers = [];
protected string $urlRegex = '/^%s\/?/u';
protected ?string $prefix = null;
protected ?string $name = null;
protected array $domains = [];
protected array $exceptionHandlers = [];
protected bool $mergeExceptionHandlers = true;
/**
* Method called to check if a domain matches
@@ -21,7 +22,7 @@ class RouteGroup extends Route implements IGroupRoute
*/
public function matchDomain(Request $request): bool
{
if ($this->domains === null || count($this->domains) === 0) {
if (count($this->domains) === 0) {
return true;
}
@@ -32,10 +33,11 @@ class RouteGroup extends Route implements IGroupRoute
return true;
}
$parameters = $this->parseParameters($domain, $request->getHost(), '.*');
$parameters = $this->parseParameters($domain, $request->getHost(), $request, '.*');
if ($parameters !== null && count($parameters) !== 0) {
$this->parameters = $parameters;
return true;
}
}
@@ -58,7 +60,7 @@ class RouteGroup extends Route implements IGroupRoute
if ($this->prefix !== null) {
/* Parse parameters from current route */
$parameters = $this->parseParameters($this->prefix, $url);
$parameters = $this->parseParameters($this->prefix, $url, $request);
/* If no custom regular expression or parameters was found on this route, we stop */
if ($parameters === null) {
@@ -72,11 +74,11 @@ class RouteGroup extends Route implements IGroupRoute
$parsedPrefix = $this->prefix;
foreach ($this->getParameters() as $parameter => $value) {
$parsedPrefix = str_ireplace('{' . $parameter . '}', $value, $parsedPrefix);
$parsedPrefix = str_ireplace('{' . $parameter . '}', (string)$value, (string)$parsedPrefix);
}
/* Skip if prefix doesn't match */
if ($this->prefix !== null && stripos($url, $parsedPrefix) === false) {
if ($this->prefix !== null && stripos($url, rtrim($parsedPrefix, '/') . '/') === false) {
return false;
}
@@ -174,6 +176,29 @@ class RouteGroup extends Route implements IGroupRoute
return $this->prefix;
}
/**
* When enabled group will overwrite any existing exception-handlers.
*
* @param bool $merge
* @return static
*/
public function setMergeExceptionHandlers(bool $merge): IGroupRoute
{
$this->mergeExceptionHandlers = $merge;
return $this;
}
/**
* Returns true if group should overwrite existing exception-handlers.
*
* @return bool
*/
public function getMergeExceptionHandlers(): bool
{
return $this->mergeExceptionHandlers;
}
/**
* Merge with information from another route.
*
@@ -187,6 +212,10 @@ class RouteGroup extends Route implements IGroupRoute
$this->setPrefix($settings['prefix'] . $this->prefix);
}
if (isset($settings['mergeExceptionHandlers']) === true) {
$this->setMergeExceptionHandlers($settings['mergeExceptionHandlers']);
}
if ($merge === false && isset($settings['exceptionHandler']) === true) {
$this->setExceptionHandlers((array)$settings['exceptionHandler']);
}
+33 -30
View File
@@ -6,28 +6,28 @@ use Pecee\Http\Request;
class RouteResource extends LoadableRoute implements IControllerRoute
{
protected $urls = [
'index' => '',
'create' => 'create',
'store' => '',
'show' => '',
'edit' => 'edit',
'update' => '',
protected array $urls = [
'index' => '',
'create' => 'create',
'store' => '',
'show' => '',
'edit' => 'edit',
'update' => '',
'destroy' => '',
];
protected $methodNames = [
'index' => 'index',
'create' => 'create',
'store' => 'store',
'show' => 'show',
'edit' => 'edit',
'update' => 'update',
protected array $methodNames = [
'index' => 'index',
'create' => 'create',
'store' => 'store',
'show' => 'show',
'edit' => 'edit',
'update' => 'update',
'destroy' => 'destroy',
];
protected $names = [];
protected $controller;
protected array $names = [];
protected string $controller;
public function __construct($url, $controller)
{
@@ -54,7 +54,7 @@ class RouteResource extends LoadableRoute implements IControllerRoute
/* Remove method/type */
if (strpos($name, '.') !== false) {
$name = (string)substr($name, 0, strrpos($name, '.'));
$name = substr($name, 0, strrpos($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
{
$url = array_search($name, $this->names, false);
if ($url !== false) {
return rtrim($this->url . $this->urls[$url], '/') . '/';
$url = parent::findUrl($method, $parameters, $name);
$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
@@ -85,7 +88,7 @@ class RouteResource extends LoadableRoute implements IControllerRoute
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;
}
@@ -99,14 +102,14 @@ class RouteResource extends LoadableRoute implements IControllerRoute
$route = rtrim($this->url, '/') . '/{id?}/{action?}';
/* Parse parameters from current route */
$this->parameters = $this->parseParameters($route, $url);
$this->parameters = $this->parseParameters($route, $url, $request);
/* If no custom regular expression or parameters was found on this route, we stop */
if ($regexMatch === null && $this->parameters === null) {
return false;
}
$action = strtolower(trim($this->parameters['action']));
$action = strtolower(trim((string)$this->parameters['action']));
$id = $this->parameters['id'];
// Remove action parameter
@@ -172,12 +175,12 @@ class RouteResource extends LoadableRoute implements IControllerRoute
$this->name = $name;
$this->names = [
'index' => $this->name . '.index',
'create' => $this->name . '.create',
'store' => $this->name . '.store',
'show' => $this->name . '.show',
'edit' => $this->name . '.edit',
'update' => $this->name . '.update',
'index' => $this->name . '.index',
'create' => $this->name . '.create',
'store' => $this->name . '.store',
'show' => $this->name . '.show',
'edit' => $this->name . '.edit',
'update' => $this->name . '.update',
'destroy' => $this->name . '.destroy',
];
+7 -2
View File
@@ -6,7 +6,12 @@ use Pecee\Http\Request;
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->setCallback($callback);
@@ -26,7 +31,7 @@ class RouteUrl extends LoadableRoute
}
/* Parse parameters from current route */
$parameters = $this->parseParameters($this->url, $url);
$parameters = $this->parseParameters($this->url, $url, $request);
/* If no custom regular expression or parameters was found on this route, we stop */
if ($regexMatch === null && $parameters === null) {
+89 -60
View File
@@ -28,50 +28,56 @@ class Router
* Current request
* @var Request
*/
protected $request;
protected Request $request;
/**
* Defines if a route is currently being processed.
* @var bool
*/
protected $isProcessingRoute;
protected bool $isProcessingRoute;
/**
* Defines all data from current processing route.
* @var ILoadableRoute
*/
protected ILoadableRoute $currentProcessingRoute;
/**
* All added routes
* @var array
*/
protected $routes = [];
protected array $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
* when a route is being processed.
* @var array
*/
protected $routeStack = [];
protected array $routeStack = [];
/**
* List of added bootmanagers
* @var array
*/
protected $bootManagers = [];
protected array $bootManagers = [];
/**
* Csrf verifier class
* @var BaseCsrfVerifier
* @var BaseCsrfVerifier|null
*/
protected $csrfVerifier;
protected ?BaseCsrfVerifier $csrfVerifier;
/**
* Get exception handlers
* @var array
*/
protected $exceptionHandlers = [];
protected array $exceptionHandlers = [];
/**
* List of loaded exception that has been loaded.
@@ -79,44 +85,44 @@ class Router
*
* @var array
*/
protected $loadedExceptionHandlers = [];
protected array $loadedExceptionHandlers = [];
/**
* Enable or disabled debugging
* @var bool
*/
protected $debugEnabled = false;
protected bool $debugEnabled = false;
/**
* The start time used when debugging is enabled
* @var float
*/
protected $debugStartTime;
protected float $debugStartTime;
/**
* List containing all debug messages
* @var array
*/
protected $debugList = [];
protected array $debugList = [];
/**
* Contains any registered event-handler.
* @var array
*/
protected $eventHandlers = [];
protected array $eventHandlers = [];
/**
* Class loader instance
* @var ClassLoader
* @var IClassLoader
*/
protected $classLoader;
protected IClassLoader $classLoader;
/**
* When enabled the router will render all routes that matches.
* When disabled the router will stop execution when first route is found.
* @var bool
*/
protected $renderMultipleRoutes = true;
protected bool $renderMultipleRoutes = false;
/**
* Router constructor.
@@ -203,7 +209,7 @@ class Router
/**
* Process added routes.
*
* @param array $routes
* @param array|IRoute[] $routes
* @param IGroupRoute|null $group
* @throws NotFoundHttpException
*/
@@ -211,11 +217,8 @@ class Router
{
$this->debug('Processing routes');
// Loop through each route-request
$exceptionHandlers = [];
// Stop processing routes if no valid route is found.
if ($this->request->getRewriteRoute() === null && $this->request->getUrl() === null) {
if ($this->request->getRewriteRoute() === null && $this->request->getUrl()->getOriginalUrl() === '') {
$this->debug('Halted route-processing as no valid route was found');
return;
@@ -223,7 +226,7 @@ class Router
$url = $this->request->getRewriteUrl() ?? $this->request->getUrl()->getPath();
/* @var $route IRoute */
// Loop through each route-request
foreach ($routes as $route) {
$this->debug('Processing route "%s"', get_class($route));
@@ -240,13 +243,22 @@ class Router
/* Add exception handlers */
if (count($route->getExceptionHandlers()) !== 0) {
/** @noinspection AdditionOperationOnArraysInspection */
$exceptionHandlers += $route->getExceptionHandlers();
if ($route->getMergeExceptionHandlers() === true) {
foreach ($route->getExceptionHandlers() as $handler) {
$this->exceptionHandlers[] = $handler;
}
} else {
$this->exceptionHandlers = $route->getExceptionHandlers();
}
}
/* Only render partial group if it matches */
if ($route instanceof IPartialGroupRoute === true) {
$this->renderAndProcess($route);
continue;
}
}
@@ -264,8 +276,6 @@ class Router
$this->processedRoutes[] = $route;
}
}
$this->exceptionHandlers = array_merge($exceptionHandlers, $this->exceptionHandlers);
}
/**
@@ -297,7 +307,7 @@ class Router
$this->debug('Rendering bootmanager "%s"', $className);
$this->fireEvents(EventHandler::EVENT_RENDER_BOOTMANAGER, [
'bootmanagers' => $this->bootManagers,
'bootmanager' => $manager,
'bootmanager' => $manager,
]);
/* Render bootmanager */
@@ -332,8 +342,12 @@ class Router
'csrfVerifier' => $this->csrfVerifier,
]);
/* Verify csrf token for request */
$this->csrfVerifier->handle($this->request);
try {
/* Verify csrf token for request */
$this->csrfVerifier->handle($this->request);
} catch (Exception $e) {
return $this->handleException($e);
}
}
$output = $this->routeRequest();
@@ -368,6 +382,9 @@ class Router
$this->debug('Matching route "%s"', get_class($route));
/* Add current processing route to constants */
$this->currentProcessingRoute = $route;
/* If the route matches */
if ($route->matchRoute($url, $this->request) === true) {
@@ -388,7 +405,7 @@ class Router
}
$this->fireEvents(EventHandler::EVENT_RENDER_MIDDLEWARES, [
'route' => $route,
'route' => $route,
'middlewares' => $route->getMiddlewares(),
]);
@@ -410,7 +427,7 @@ class Router
$routeOutput = $route->renderRoute($this->request, $this);
if ($this->renderMultipleRoutes === true) {
if ($routeOutput !== null) {
if ($routeOutput !== '') {
return $routeOutput;
}
@@ -427,12 +444,12 @@ class Router
}
} catch (Exception $e) {
$this->handleException($e);
return $this->handleException($e);
}
if ($methodNotAllowed === true) {
$message = sprintf('Route "%s" or method "%s" not allowed.', $this->request->getUrl()->getPath(), $this->request->getMethod());
$this->handleException(new NotFoundHttpException($message, 403));
return $this->handleException(new NotFoundHttpException($message, 403));
}
if (count($this->request->getLoadedRoutes()) === 0) {
@@ -483,7 +500,7 @@ class Router
$this->request->setHasPendingRewrite(false);
$this->fireEvents(EventHandler::EVENT_REWRITE, [
'rewriteUrl' => $this->request->getRewriteUrl(),
'rewriteUrl' => $this->request->getRewriteUrl(),
'rewriteRoute' => $this->request->getRewriteRoute(),
]);
@@ -504,7 +521,7 @@ class Router
$this->debug('Starting exception handling for "%s"', get_class($e));
$this->fireEvents(EventHandler::EVENT_LOAD_EXCEPTIONS, [
'exception' => $e,
'exception' => $e,
'exceptionHandlers' => $this->exceptionHandlers,
]);
@@ -516,8 +533,8 @@ class Router
}
$this->fireEvents(EventHandler::EVENT_RENDER_EXCEPTION, [
'exception' => $e,
'exceptionHandler' => $handler,
'exception' => $e,
'exceptionHandler' => $handler,
'exceptionHandlers' => $this->exceptionHandlers,
]);
@@ -539,7 +556,7 @@ class Router
$this->debug('Exception handler contains rewrite, reloading routes');
$this->fireEvents(EventHandler::EVENT_REWRITE, [
'rewriteUrl' => $this->request->getRewriteUrl(),
'rewriteUrl' => $this->request->getRewriteUrl(),
'rewriteRoute' => $this->request->getRewriteRoute(),
]);
@@ -575,7 +592,6 @@ class Router
'name' => $name,
]);
/* @var $route ILoadableRoute */
foreach ($this->processedRoutes as $route) {
/* 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 */
if (is_string($name) === true && strpos($name, '@') !== false) {
if (strpos($name, '@') !== false) {
[$controller, $method] = array_map('strtolower', explode('@', $name));
if ($controller === strtolower($route->getClass()) && $method === strtolower($route->getMethod())) {
if ($controller === strtolower((string)$route->getClass()) && $method === strtolower((string)$route->getMethod())) {
$this->debug('Found route "%s" by controller "%s" and method "%s"', $route->getUrl(), $controller, $method);
return $route;
@@ -605,7 +621,7 @@ class Router
/* Check if callback matches (if it's not a function) */
$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 */
if (strpos($callback, $name) === 0 || strtolower($callback) === strtolower($name)) {
@@ -651,15 +667,11 @@ class Router
$this->debug('Finding url', func_get_args());
$this->fireEvents(EventHandler::EVENT_GET_URL, [
'name' => $name,
'name' => $name,
'parameters' => $parameters,
'getParams' => $getParams,
'getParams' => $getParams,
]);
if ($getParams !== null && is_array($getParams) === false) {
throw new InvalidArgumentException('Invalid type for getParams. Must be array or null');
}
if ($name === '' && $parameters === '') {
return new Url('/');
}
@@ -684,7 +696,7 @@ class Router
->setParams($getParams);
}
if($name !== null) {
if ($name !== null) {
/* We try to find a match on the given name */
$route = $this->findRoute($name);
@@ -703,21 +715,21 @@ class Router
/* Loop through all the routes to see if we can find a match */
/* @var $route ILoadableRoute */
foreach ($this->processedRoutes as $route) {
foreach ($this->processedRoutes as $processedRoute) {
/* Check if the route contains the name/alias */
if ($route->hasName($controller) === true) {
if ($processedRoute->hasName($controller) === true) {
return $this->request
->getUrlCopy()
->setPath($route->findUrl($method, $parameters, $name))
->setPath($processedRoute->findUrl($method, $parameters, $name))
->setParams($getParams);
}
/* 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
->getUrlCopy()
->setPath($route->findUrl($method, $parameters, $name))
->setPath($processedRoute->findUrl($method, $parameters, $name))
->setParams($getParams);
}
@@ -842,7 +854,7 @@ class Router
/**
* Get class loader
*
* @return ClassLoader
* @return IClassLoader
*/
public function getClassLoader(): IClassLoader
{
@@ -901,8 +913,8 @@ class Router
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
$this->debugList[] = [
'message' => vsprintf($message, $args),
'time' => number_format(microtime(true) - $this->debugStartTime, 10),
'trace' => end($trace),
'time' => number_format(microtime(true) - $this->debugStartTime, 10),
'trace' => end($trace),
];
}
@@ -929,6 +941,16 @@ class Router
return $this->debugList;
}
/**
* Get the current processing route details.
*
* @return ILoadableRoute
*/
public function getCurrentProcessingRoute(): ILoadableRoute
{
return $this->currentProcessingRoute;
}
/**
* Changes the rendering behavior of the router.
* When enabled the router will render all routes that matches.
@@ -944,4 +966,11 @@ class Router
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\IEventHandler;
use Pecee\SimpleRouter\Route\IGroupRoute;
use Pecee\SimpleRouter\Route\ILoadableRoute;
use Pecee\SimpleRouter\Route\IPartialGroupRoute;
use Pecee\SimpleRouter\Route\IRoute;
use Pecee\SimpleRouter\Route\RouteController;
@@ -36,19 +37,19 @@ class SimpleRouter
* Default namespace added to all routes
* @var string|null
*/
protected static $defaultNamespace;
protected static ?string $defaultNamespace = null;
/**
* The response object
* @var Response
* @var Response|null
*/
protected static $response;
protected static ?Response $response = null;
/**
* Router instance
* @var Router
*/
protected static $router;
protected static ?Router $router = null;
/**
* Start routing
@@ -173,7 +174,7 @@ class SimpleRouter
*/
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);
});
}
@@ -185,7 +186,7 @@ class SimpleRouter
* @param string|array|Closure $callback
* @param array|null $settings
*
* @return RouteUrl
* @return RouteUrl|IRoute
*/
public static function get(string $url, $callback, array $settings = null): IRoute
{
@@ -198,7 +199,7 @@ class SimpleRouter
* @param string $url
* @param string|array|Closure $callback
* @param array|null $settings
* @return RouteUrl
* @return RouteUrl|IRoute
*/
public static function post(string $url, $callback, array $settings = null): IRoute
{
@@ -211,7 +212,7 @@ class SimpleRouter
* @param string $url
* @param string|array|Closure $callback
* @param array|null $settings
* @return RouteUrl
* @return RouteUrl|IRoute
*/
public static function put(string $url, $callback, array $settings = null): IRoute
{
@@ -224,7 +225,7 @@ class SimpleRouter
* @param string $url
* @param string|array|Closure $callback
* @param array|null $settings
* @return RouteUrl
* @return RouteUrl|IRoute
*/
public static function patch(string $url, $callback, array $settings = null): IRoute
{
@@ -237,7 +238,7 @@ class SimpleRouter
* @param string $url
* @param string|array|Closure $callback
* @param array|null $settings
* @return RouteUrl
* @return RouteUrl|IRoute
*/
public static function options(string $url, $callback, array $settings = null): IRoute
{
@@ -250,7 +251,7 @@ class SimpleRouter
* @param string $url
* @param string|array|Closure $callback
* @param array|null $settings
* @return RouteUrl
* @return RouteUrl|IRoute
*/
public static function delete(string $url, $callback, array $settings = null): IRoute
{
@@ -262,15 +263,11 @@ class SimpleRouter
*
* @param array $settings
* @param Closure $callback
* @return RouteGroup
* @return RouteGroup|IGroupRoute
* @throws InvalidArgumentException
*/
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->setCallback($callback);
$group->setSettings($settings);
@@ -287,15 +284,11 @@ class SimpleRouter
* @param string $url
* @param Closure $callback
* @param array $settings
* @return RoutePartialGroup
* @return RoutePartialGroup|IPartialGroupRoute
* @throws InvalidArgumentException
*/
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;
$group = new RoutePartialGroup();
@@ -313,7 +306,7 @@ class SimpleRouter
* @param string $url
* @param string|array|Closure $callback
* @param array|null $settings
* @return RouteUrl
* @return RouteUrl|IRoute
* @see SimpleRouter::form
*/
public static function basic(string $url, $callback, array $settings = null): IRoute
@@ -328,7 +321,7 @@ class SimpleRouter
* @param string $url
* @param string|array|Closure $callback
* @param array|null $settings
* @return RouteUrl
* @return RouteUrl|IRoute
* @see SimpleRouter::form
*/
public static function form(string $url, $callback, array $settings = null): IRoute
@@ -427,9 +420,7 @@ class SimpleRouter
{
$callbackHandler = new CallbackExceptionHandler($callback);
static::router()->addRoute(
(new RouteGroup())->addExceptionHandler($callbackHandler)
);
static::router()->addExceptionHandler($callbackHandler);
return $callbackHandler;
}
@@ -501,8 +492,8 @@ class SimpleRouter
/**
* Prepends the default namespace to all new routes added.
*
* @param IRoute $route
* @return IRoute
* @param ILoadableRoute|IRoute $route
* @return IRoute|ILoadableRoute
*/
public static function addDefaultNamespace(IRoute $route): IRoute
{
@@ -2,12 +2,12 @@
class DummyCsrfVerifier extends \Pecee\Http\Middleware\BaseCsrfVerifier {
protected $except = [
protected array $except = [
'/exclude-page',
'/exclude-all/*',
];
protected $include = [
protected array $include = [
'/exclude-all/include-page',
];
@@ -2,12 +2,12 @@
class IpRestrictMiddleware extends \Pecee\Http\Middleware\IpRestrictAccess {
protected $ipBlacklist = [
protected array $ipBlacklist = [
'5.5.5.5',
'8.8.*',
];
protected $ipWhitelist = [
protected array $ipWhitelist = [
'8.8.2.2',
];
@@ -0,0 +1,11 @@
<?php
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;
$stack = [];
TestRouter::group(['exceptionHandler' => [ExceptionHandlerFirst::class, ExceptionHandlerSecond::class]], function () use ($stack) {
TestRouter::group(['exceptionHandler' => [ExceptionHandlerFirst::class, ExceptionHandlerSecond::class]], function () {
TestRouter::group(['exceptionHandler' => ExceptionHandlerThird::class], function () use ($stack) {
TestRouter::group(['prefix' => '/test', 'exceptionHandler' => ExceptionHandlerThird::class], function () {
TestRouter::get('/my-path', 'DummyController@method1');
@@ -43,7 +43,7 @@ class RouterRewriteTest extends \PHPUnit\Framework\TestCase
});
try {
TestRouter::debug('/my-non-existing-path', 'get');
TestRouter::debug('/test/non-existing', 'get');
} catch (\ResponseException $e) {
}
@@ -58,6 +58,33 @@ class RouterRewriteTest extends \PHPUnit\Framework\TestCase
}
public function testStopMergeExceptionHandlers()
{
global $stack;
$stack = [];
TestRouter::group(['prefix' => '/', 'exceptionHandler' => ExceptionHandlerFirst::class], function () {
TestRouter::group(['prefix' => '/admin', 'exceptionHandler' => ExceptionHandlerSecond::class, 'mergeExceptionHandlers' => false], function () {
TestRouter::get('/my-path', 'DummyController@method1');
});
});
try {
TestRouter::debug('/admin/my-path-test', 'get');
} catch (\Pecee\SimpleRouter\Exceptions\NotFoundHttpException $e) {
}
$expectedStack = [
ExceptionHandlerSecond::class,
];
$this->assertEquals($expectedStack, $stack);
}
public function testRewriteExceptionMessage()
{
$this->expectException(\Pecee\SimpleRouter\Exceptions\NotFoundHttpException::class);
@@ -2,6 +2,7 @@
require_once 'Dummy/DummyMiddleware.php';
require_once 'Dummy/DummyController.php';
require_once 'Dummy/NSController.php';
require_once 'Dummy/Exception/ExceptionHandlerException.php';
class RouterRouteTest extends \PHPUnit\Framework\TestCase
@@ -174,6 +175,70 @@ class RouterRouteTest extends \PHPUnit\Framework\TestCase
TestRouter::debug('/test', 'get');
$this->assertFalse($result);
}
public function testFixedSubdomainDynamicDomain()
{
TestRouter::request()->setHost('other.world.com');
$result = false;
TestRouter::group(['domain' => 'other.{domain}'], function () use (&$result) {
TestRouter::get('/test', function ($domain = null) use (&$result) {
$result = true;
});
});
TestRouter::debug('/test', 'get');
$this->assertTrue($result);
}
public function testFixedSubdomainDynamicDomainParameter()
{
TestRouter::request()->setHost('other.world.com');
$result = false;
TestRouter::group(['domain' => 'other.{domain}'], function () use (&$result) {
TestRouter::get('/test', 'DummyController@param');
TestRouter::get('/test/{key}', 'DummyController@param');
});
$response = TestRouter::debugOutputNoReset('/test', 'get');
$this->assertEquals('world.com', $response);
$response = TestRouter::debugOutput('/test/unittest', 'get');
$this->assertEquals('unittest, world.com', $response);
}
public function testWrongFixedSubdomainDynamicDomain()
{
TestRouter::request()->setHost('wrong.world.com');
$result = false;
TestRouter::group(['domain' => 'other.{domain}'], function () use (&$result) {
TestRouter::get('/test', function ($domain = null) use (&$result) {
$result = true;
});
});
try {
TestRouter::debug('/test', 'get');
} catch(\Exception $e) {
}
$this->assertFalse($result);
}
@@ -246,6 +311,16 @@ class RouterRouteTest extends \PHPUnit\Framework\TestCase
$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()
{
TestRouter::get('/recipe', 'DummyController@method1')->name('add');
+53 -17
View File
@@ -27,6 +27,23 @@ class RouterUrlTest extends \PHPUnit\Framework\TestCase
TestRouter::router()->reset();
}
public function testLastParameterSlash()
{
TestRouter::get('/test/{param}', function ($param) {
return $param;
})->setSettings(['includeSlash' => true]);
// Test with ending /
$output = TestRouter::debugOutputNoReset('/test/param/');
$this->assertEquals($output, 'param/');
// Test without ending /
$output = TestRouter::debugOutputNoReset('/test/param');
$this->assertEquals($output, 'param');
TestRouter::router()->reset();
}
public function testUnicodeCharacters()
{
// Test spanish characters
@@ -181,6 +198,22 @@ class RouterUrlTest extends \PHPUnit\Framework\TestCase
$output = TestRouter::debugOutput('/admin/asd/bec/123', 'get');
$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()
@@ -218,9 +251,9 @@ class RouterUrlTest extends \PHPUnit\Framework\TestCase
TestRouter::debug('/');
$this->assertCount(2, $result);
$this->assertCount(2, $result);
}
public function testDefaultNamespace()
{
TestRouter::setDefaultNamespace('\\Default\\Namespace');
@@ -229,14 +262,14 @@ class RouterUrlTest extends \PHPUnit\Framework\TestCase
TestRouter::group([
'namespace' => 'Appended\Namespace',
'prefix' => '/horses',
'prefix' => '/horses',
], function () {
TestRouter::get('/', 'DummyController@method1');
TestRouter::group([
'namespace' => '\\New\\Namespace',
'prefix' => '/race',
'prefix' => '/race',
], function () {
TestRouter::get('/', 'DummyController@method1');
@@ -271,13 +304,14 @@ class RouterUrlTest extends \PHPUnit\Framework\TestCase
TestRouter::router()->reset();
}
public function testGroupPrefix() {
public function testGroupPrefix()
{
$result = false;
TestRouter::group(['prefix' => '/lang/{lang}'], function () use(&$result) {
TestRouter::group(['prefix' => '/lang/{lang}'], function () use (&$result) {
TestRouter::get('/test', function() use(&$result) {
TestRouter::get('/test', function () use (&$result) {
$result = true;
});
});
@@ -291,13 +325,13 @@ class RouterUrlTest extends \PHPUnit\Framework\TestCase
$result = null;
$expectedResult = 28;
TestRouter::group(['prefix' => '/lang/{lang}'], function () use(&$result) {
TestRouter::group(['prefix' => '/lang/{lang}'], function () use (&$result) {
TestRouter::get('/horse/{horseType}', function($horseType) use(&$result) {
TestRouter::get('/horse/{horseType}', function ($horseType) use (&$result) {
$result = false;
});
TestRouter::get('/user/{userId}', function($userId) use(&$result) {
TestRouter::get('/user/{userId}', function ($userId) use (&$result) {
$result = $userId;
});
});
@@ -308,14 +342,15 @@ class RouterUrlTest extends \PHPUnit\Framework\TestCase
}
public function testPassParameter() {
public function testPassParameter()
{
$result = false;
$expectedLanguage = 'da';
TestRouter::group(['prefix' => '/lang/{lang}'], function ($language) use(&$result) {
TestRouter::group(['prefix' => '/lang/{lang}'], function ($language) use (&$result) {
TestRouter::get('/test', function($language) use(&$result) {
TestRouter::get('/test', function ($language) use (&$result) {
$result = $language;
});
@@ -327,15 +362,16 @@ class RouterUrlTest extends \PHPUnit\Framework\TestCase
}
public function testPassParameterDeep() {
public function testPassParameterDeep()
{
$result = false;
$expectedLanguage = 'da';
TestRouter::group(['prefix' => '/lang/{lang}'], function ($language) use(&$result) {
TestRouter::group(['prefix' => '/lang/{lang}'], function ($language) use (&$result) {
TestRouter::group(['prefix' => '/admin'], function($language) use(&$result) {
TestRouter::get('/test', function($language) use(&$result) {
TestRouter::group(['prefix' => '/admin'], function ($language) use (&$result) {
TestRouter::get('/test', function ($language) use (&$result) {
$result = $language;
});
});
+20 -5
View File
@@ -8,7 +8,7 @@ class TestRouter extends \Pecee\SimpleRouter\SimpleRouter
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();
@@ -18,22 +18,24 @@ class TestRouter extends \Pecee\SimpleRouter\SimpleRouter
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 {
static::debugNoReset($testUrl, $testMethod);
} catch(\Exception $e) {
} catch (\Exception $e) {
static::$defaultNamespace = null;
static::router()->reset();
throw $e;
}
if($reset === true) {
if ($reset === true) {
static::$defaultNamespace = null;
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;
@@ -46,4 +48,17 @@ class TestRouter extends \Pecee\SimpleRouter\SimpleRouter
return $response;
}
public static function debugOutputNoReset(string $testUrl, string $testMethod = 'get', bool $reset = true): string
{
$response = null;
// Route request
ob_start();
static::debugNoReset($testUrl, $testMethod, $reset);
$response = ob_get_clean();
// Return response
return $response;
}
}