Compare commits

...

452 Commits

Author SHA1 Message Date
Simon 301d551981 Fix host url 2024-11-10 23:20:08 +01:00
Simon 90418eb41c Bugfixes
- Fixed host not set on request url.
- Url returns relativeUrl when calling toString() to avoid any issues when using in RouteUrl(url()) for rewrites.
2024-11-06 11:05:43 +01:00
Simon 0634ba79dc Fixed getUrl() not working for home urls. 2023-12-11 22:45:14 +01:00
Simon 3534233a76 Fixed included triggering on other request-types than post 2023-12-09 18:39:33 +01:00
Simon 0f55480156 Fixed rewrite-route not being executed in rare instances 2023-12-09 05:36:30 +01:00
Simon 99ed44eb1e Fixed setHost nullable value 2023-11-29 01:20:00 +01:00
Simon 565a926bd3 Remove port from hostname 2023-11-27 08:06:35 +01:00
Simon 64483652ff Strip any potential port number from hostname 2023-11-27 07:58:49 +01:00
Simon d17ee96221 Added better handling of domains on urls. 2023-11-27 06:53:33 +01:00
Simon ed1ed43484 - Fixed domains not being prepending properly to urls.
- Won't prepend subdomain to urls if subdomain is equal to current host.
- Url: implemented parse function.
- Router: getUrl now parses url to properly parse the subdomain.
2023-11-27 05:52:10 +01:00
Simon a275366a90 Fixed inputhandler overwriting post-values 2023-11-21 22:25:06 +01:00
Simon Sessingø 4d1caddce4 Merge pull request #683 from ms-afk/bugfix-rewrite-route-executed-twice
Fixed rare double execution of rewrite routes in exception handler
2023-11-21 16:11:56 +01:00
Simon Sessingø 0970bd00c6 Merge pull request #682 from ms-afk/fix-readme-php-di
Fixed the php-di integration example in the README
2023-11-21 16:11:06 +01:00
Simon Sessingø 49b132da93 Merge pull request #672 from ATC-4K/patch-1
Added @return never to Response.php
2023-11-21 16:10:16 +01:00
Simon 08d78c8f71 Added support for input stream when not json encoded 2023-11-21 16:08:22 +01:00
ms-afk 5986dc9a08 fixed rare double execution of rewrite routes in exception handler
If a rewrite route is present, Router's method handleException will, currently, be adding that route to the processedRoutes array without removing the hasPendingRewrite flag. This leads to the associated callback being executed twice if the callback itself returns NULL. This happens because the handleRouteRewrite method, finding that hasPendingRewrite is still set to true, adds the rewriteRoute to the processedRoutes for a second time, before finally setting that flag to false.
2023-10-03 12:26:41 +02:00
Marco Scagnol cdf165d0f4 Fixed the php-di integration example
The previous version of the example in the README used exceptions that were not imported (NotFoundHttpException) to wrap existing exceptions. I've adapted the example to be similar to the current version of Pecee\SimpleRouter\ClassLoader\ClassLoader. That includes returning strings from loadClassMethod and loadClosure and not wrapping every exception, thrown by the called functions, into NotFoundHttpException exceptions, as well as using the exception ClassNotFoundHttpException when a class cannot be found, instead of the generic NotFoundHttpException. I also changed the way the ClassLoader checks if the container can resolve the class by using the container's method "has" instead of the php function class_exists.
2023-09-30 16:22:52 +02:00
ATC-4K adfe70f191 Added @return never to Response.php
In PHP8.1 instead of :void :never would be returned.
As this project is PHP7.4 compatible, we add it as a PHPDOC, so IDEs using this project will no longer complain and automatic checks will successfully detect dead code after calling a redirect()
2023-07-10 21:16:32 +02:00
Simon Sessingø cd891d5334 Merge pull request #671 from skipperbent/v5-release
V5 release
2023-05-06 21:14:29 +02:00
Simon Sessingø 7feb464af1 Merge pull request #670 from skipperbent/v5-development
Version 5.3.0.5
2023-05-06 21:11:12 +02:00
sessingo d3b1577095 RouteResource: simplified the findUrl method (issue: #666) 2023-05-06 21:07:58 +02:00
Simon Sessingø 12b6e3c1ab Merge pull request #669 from skipperbent/v5-release
V5 release
2023-05-06 17:39:41 +02:00
Simon Sessingø f085134ae3 Merge pull request #668 from skipperbent/v5-development
Version 5.3.0.4
2023-05-06 17:37:00 +02:00
Simon Sessingø 00d1c534de Merge pull request #667 from skipperbent/fix-resource-parameters
Resource-type not respecting parameters when using getUrl (issue: #666)
2023-05-06 17:33:53 +02:00
Simon Sessingø 37f826f24c Merge pull request #664 from SunflowerFuchs/v5-development
Fixed Route::setUrl() behavior
2023-05-06 17:32:57 +02:00
sessingo f3c6015a59 Fixed Resource-type not respecting parameters when using getUrl (issue: #666) 2023-05-06 17:29:33 +02:00
Pascal Pirschel b2851e41f1 Fixed Route::setUrl() behavior
When there are no parameters in the url, correctly empty the routes parameter array
2023-05-05 13:48:26 +02:00
Simon Sessingø 8ffa1088ab Merge pull request #662 from skipperbent/v5-release
V5 release
2023-04-24 20:08:29 +02:00
Simon Sessingø 9b8843aa08 Merge pull request #661 from skipperbent/v5-development
Reverted exception handling to old behavior (issue: #660)
2023-04-24 20:08:17 +02:00
sessingo e105f266e3 Fixed typo 2023-04-24 20:05:33 +02:00
sessingo a49d7c13b6 Reverted exception handling to old behavior (issue: #660) 2023-04-24 20:03:15 +02:00
Simon Sessingø f565014dff Merge pull request #659 from skipperbent/v5-release
V5 release
2023-04-21 11:38:08 +02:00
Simon Sessingø ad765b9856 Merge pull request #658 from skipperbent/v5-development
InputItem: php8.1 deprecated warning
2023-04-21 11:35:24 +02:00
sessingo 4778a8f29e InputItem: php8.1 deprecated warning-Added returnTypeWillChange to offsetGet 2023-04-21 11:28:19 +02:00
Simon Sessingø 97b61fb8bf Merge pull request #656 from skipperbent/v5-release
V5 release
2023-04-08 20:01:07 +02:00
Simon Sessingø 847cb3e273 Merge pull request #655 from skipperbent/v5-development
Version 5.3.0.1
2023-04-08 20:00:55 +02:00
Simon Sessingø 2b4ae2b211 Merge pull request #654 from skipperbent/v5-exception-handling
PHP8: better exception handling
2023-04-08 19:56:55 +02:00
sessingo fa05d64a76 PHP8: better exception handling
Looks like PHP8 handles exceptions differently with Throwables used in cases where php-error occoured.
To fix this Throwable are now used to catch exception in routeRequest and any instance of Throwable will be converted to Exception.
2023-04-08 19:49:51 +02:00
Simon Sessingø 0ff9258776 Merge pull request #653 from skipperbent/v5-release
V5 release
2023-04-07 15:41:17 +02:00
Simon Sessingø b937b610de Merge pull request #651 from skipperbent/v5-development
Version 5.3.0.0
2023-04-07 15:41:04 +02:00
Simon Sessingø 5ac747374b Merge pull request #652 from skipperbent/fix-tostring
[BUGFIX] String return type
2023-04-07 15:38:41 +02:00
sessingo d6642a7f7b Changed behavior of router to always exspect returned output to be string. 2023-04-07 15:30:24 +02:00
Simon Sessingø ebf9224407 Merge pull request #649 from skipperbent/feature-csrfverifier
[!!!] CsrfVerifier changes
2023-04-07 14:36:13 +02:00
Simon Sessingø c635771fcd Merge pull request #650 from skipperbent/v5-fix-exception
[BUGFIX] Exception handling improvements
2023-04-07 14:35:56 +02:00
sessingo 791d69b24d Updated documentation 2023-04-07 13:08:40 +02:00
sessingo aa654a3ac6 [BUGFIX] Fixed exception-handler rewrite not always triggered 2023-04-07 13:05:27 +02:00
sessingo 6c6d81d3c9 [!!!] CsrfVerifier changes
- [!!!] Made $except and $include array not nullable.
- Added more customizable BaseCsrfVerifier. Can now be used as ticket for no hotlinking etc.
2023-04-06 13:09:26 +02:00
Simon Sessingø 5dc3e99d6e Merge pull request #648 from skipperbent/v5-release
V5 release
2023-04-02 03:24:30 +02:00
Simon Sessingø fadb783d3c Merge pull request #647 from skipperbent/v5-development
Fixed Response not initialized + incorrect phpDoc.
2023-04-02 03:24:14 +02:00
sessingo 8c79b74e14 Bugfixes 2023-04-02 03:22:38 +02:00
Simon Sessingø 578fa10fc9 Merge pull request #646 from skipperbent/v5-release
V5 release
2023-04-02 03:20:13 +02:00
Simon Sessingø 8477ea19d4 Merge pull request #645 from skipperbent/v5-development
Version 5.2.0.0
2023-04-02 03:20:01 +02:00
Simon Sessingø 77da37e00e Merge pull request #644 from skipperbent/v5-property-types
[!!!] Added type definitions to property types
2023-04-02 03:14:50 +02:00
sessingo 5946397c15 [!!!] Added type definitions to property types
- Request: optimized getIp method and reversed the order so proxy is always checked first.
2023-04-02 03:10:27 +02:00
Simon Sessingø 72ebada821 Merge pull request #643 from skipperbent/v5-release
V5 release
2023-03-30 16:27:25 +02:00
Simon Sessingø 4121011ef2 Merge pull request #642 from skipperbent/v5-development
Version 5.1.1.0
2023-03-30 16:26:44 +02:00
Simon Sessingø dc3b1fe74e Merge pull request #641 from skipperbent/fix-session
[BUGFIX] Fixed SESSION not shutdown correctly when using redirect.
2023-03-30 16:21:04 +02:00
sessingo c622ef97b0 [BUGFIX] Fixed SESSION not shutdown correctly when using redirect.
- Disabled multiple route rendering by default.
2023-03-30 16:15:06 +02:00
Simon Sessingø e5b5b0898f Merge pull request #640 from skipperbent/v5-release
V5 release
2023-03-25 03:21:27 +01:00
Simon Sessingø 5dbfc3dbfe Merge pull request #639 from skipperbent/v5-development
Version 5.1.0.0
2023-03-25 03:19:13 +01:00
Simon Sessingø 8be42ca1a6 Merge pull request #638 from skipperbent/v5-feature-ending-slash
[FEATURE] Parameter ending trail/slash
2023-03-25 03:13:28 +01:00
sessingo 4a7360909c Fixed for PHP7 2023-03-25 02:24:19 +01:00
sessingo 74c52931e9 Ending trail/slash feature
- Feature: added support for slash in parameters (see readme).
- Route: Fixed hardcoded param modifier.
- Route: optimisations.
- Updated Readme.
2023-03-25 02:20:06 +01:00
Simon Sessingø 515fbc173c Merge pull request #633 from skipperbent/v5-release
V5 release
2023-02-13 14:06:39 +01:00
Simon Sessingø 0aea8673d9 Merge pull request #632 from skipperbent/v5-development
Version 5.0.0.3
2023-02-13 14:06:24 +01:00
sessingo 5ab5087f8e Fixed CSRF-token not triggering exception handlers. 2023-02-13 13:59:49 +01:00
sessingo b7c1b52a57 Updated phpstan 2023-02-12 23:39:49 +01:00
Simon Sessingø 89b766ff2f Merge pull request #631 from skipperbent/v5-release
V5 release
2023-02-11 17:35:00 +01:00
Simon Sessingø 9c66a4dfd8 Merge pull request #630 from skipperbent/v5-development
Version 5.0.0.2
2023-02-11 17:34:48 +01:00
sessingo 941149d8d7 Fixed deprication warnings 2023-02-11 17:31:00 +01:00
sessingo 1764b67112 Updated composer.json 2023-02-11 17:25:54 +01:00
sessingo 3742998537 Updated git workflows 2023-02-11 17:22:40 +01:00
sessingo 20e00efbed Php8 deprication warning 2023-02-11 17:16:40 +01:00
sessingo 7dd176a771 Fixed strtolower php8 deprication warning 2023-02-10 22:47:45 +01:00
sessingo abda9d468b Fixed deprication message when using response()->json 2023-02-10 08:48:58 +01:00
sessingo 23a29ce5d1 Fixed php strtolower deprication warning 2023-02-10 06:10:29 +01:00
Simon Sessingø 0cb7fc416d Merge pull request #629 from skipperbent/v5-release
V5 release
2023-02-09 03:35:29 +01:00
Simon Sessingø d5bf77cbd4 Merge pull request #628 from skipperbent/v5-development
Fixed offsetGet return type deprication warning
2023-02-09 03:34:48 +01:00
sessingo e34fe47a04 Fixed offsetGet return type deprication warning 2023-02-09 03:29:41 +01:00
Simon Sessingø 1e9fa9c6a1 Merge pull request #627 from skipperbent/v4-development
Version 5.0.0.0
2023-02-09 03:07:54 +01:00
sessingo f0a4b6e46f Updated composer 2023-02-09 03:01:17 +01:00
Simon Sessingø e38a406957 Merge pull request #594 from redoonetworks/dymanic-domain
Dynamic domain, fixed Subdomain
2023-02-09 02:32:18 +01:00
Simon Sessingø 0630569f56 Merge pull request #592 from skipperbent/master
fixed json_encode 2nd parameter int flag issue in response and issue with offsetGet return type incompatibility
2023-02-09 02:30:02 +01:00
Simon Sessingø b82e29c864 Merge pull request #614 from DeveloperMarius/non-ascii-chars-urlencoding
urlencoding issue with non-ASCII chars in request-uri header
2023-02-09 02:29:25 +01:00
Simon Sessingø 9c79901316 Merge pull request #604 from xJuvi/xJuvi-patch-1
Make current processing rule accessible
2023-02-09 02:28:34 +01:00
Simon Sessingø fbc87cc9bd Merge pull request #589 from mauroagr/patch-1
Update in error status code
2023-02-09 02:26:36 +01:00
DeveloperMarius 301c2cfe4a fixed urldecode request-uri header 2022-03-01 15:39:31 +01:00
Hannes 01bad94af0 Update Router.php 2022-02-02 15:20:23 +01:00
Hannes a1d5f38af7 Add function to fetch currect processing route 2022-01-02 21:59:39 +01:00
Hannes b5e42dbdfb Make current processing rule accessible
Adds an  constant with the data from the current processing rule to make it globally accessible, f.e. in middleware objects
2022-01-02 14:42:43 +01:00
Stefan Warnat 06a63eb0e7 Fix Typo 2021-10-04 02:02:45 +02:00
Stefan Warnat 5268a998ff Extend domain filter to support fixed subdomain and domain parameter 2021-10-04 01:49:42 +02:00
Stefan Warnat 9fa7ad3e91 implement test Function to test output without reset 2021-10-04 01:49:00 +02:00
Mauro Tschiedel 0097725ef2 Update in error status code
Add example If you will add specific status code for the browser
2021-09-01 08:22:12 -03:00
Simon Sessingø 749f252ffb Merge pull request #584 from skipperbent/v4-release
V4 release
2021-07-18 01:45:46 +02:00
Simon Sessingø 032a2ae7e0 Merge pull request #583 from skipperbent/v4-development
Version 4.3.7.2
2021-07-18 01:45:36 +02:00
Simon Sessingø c2e2d3bb5d Merge pull request #582 from skipperbent/v4-group-prefix-bug
Fixed issue causing group prefix to trigger on paths without "/" (issue #573 - thanks @Venloress)
2021-07-18 01:44:57 +02:00
Simon Sessingø 5dd0690009 Fixed issue causing group prefix to trigger on paths without "/" (issue #573 - thanks @Venloress). 2021-07-18 01:41:26 +02:00
Simon Sessingø dbcf8f19a3 Merge pull request #581 from skipperbent/v4-release
V4 release
2021-07-17 22:01:09 +02:00
Simon Sessingø ee61eda1e8 Merge pull request #580 from skipperbent/v4-development
Updated documentation
2021-07-17 22:00:22 +02:00
Simon Sessingø 471bbe137f Updated documentation 2021-07-17 21:59:23 +02:00
Simon Sessingø b08dea9da5 Merge pull request #579 from skipperbent/v4-release
V4 release
2021-07-17 21:57:11 +02:00
Simon Sessingø b17ba06a8c Merge pull request #578 from skipperbent/v4-development
Version 4.3.7.0
2021-07-17 21:56:47 +02:00
Simon Sessingø 69494265a5 Merge pull request #577 from skipperbent/v4-prevent-merge-attribute
Added group attribute to stop router from merging exception-handlers (issue: #573)
2021-07-17 21:54:07 +02:00
Simon Sessingø 0d8915b206 Fixed return type. 2021-07-17 21:52:00 +02:00
Simon Sessingø b54a25804a Added group attribute to stop router from merging exception-handlers (issue: #573)
- Added new mergeExceptionHandlers attribute to stop router from merging exception-handlers.
- RouteGroup: Added setMergeExceptionHandlers and getMergeExceptionHandlers methods.
- IRouteGroup: Added setMergeExceptionHandlers and getMergeExceptionHandlers method.
- Updated documentation to reflect changes.
- Added unit-tests.
2021-07-17 21:46:05 +02:00
Simon Sessingø e3145cc1ec Merge pull request #576 from skipperbent/v4-release
V4 release
2021-07-16 22:31:20 +02:00
Simon Sessingø 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ø b7c31ae434 Merge pull request #553 from skipperbent/v4-development
Version 4.3.4.1
2021-05-19 04:43:29 +02:00
Simon Sessingø 0c329e4c5b Merge pull request #552 from skipperbent/v4-exception-callback-fix
SimpleRouter::error not working within group
2021-05-19 04:42:35 +02:00
Simon Sessingø e057a76153 Removed unused variable. 2021-05-19 04:41:36 +02:00
Simon Sessingø f7f1f1e3de Fixed issue causing SimpleRouter::exception helper not to work when used within a group (issue: #551) 2021-05-19 04:31:50 +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ø f93621fa02 Merge pull request #549 from skipperbent/v4-development
Version 4.3.4.0
2021-05-19 00:45:32 +02:00
Simon Sessingø 5ab8826bfb Merge pull request #548 from skipperbent/v4-inputitem-array
InputHandler optimisations.
2021-05-19 00:42:48 +02:00
Simon Sessingø f863f931d8 Fixed php8 php-unit tests compatibility. 2021-05-18 18:08:41 +02:00
Simon Sessingø 0d6326dfbb InputHandler optimisations.
- InputItem can now be used like array (for example: input()->get('items')[0]) if value is array.
- Changed default-value parameter for get, post, file can now be mixed to allow object as return-type.
2021-05-18 18:00:18 +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ø 448a423d5d Merge pull request #544 from skipperbent/v4-development
Version 4.3.3.0
2021-05-02 14:00:10 +02:00
Simon Sessingø df9a855579 Merge pull request #543 from skipperbent/v4-bugfixes
Bugfixes
2021-05-02 13:58:00 +02:00
Simon Sessingø d6bd9bbd72 Development
- [FEATURE] Namespace overwrite now works globally. 'Service¨' will append namespace whereas '\Service' will overwrite it.
- [FEATURE] Exceptionhandlers are now rendered in reverse order from newest to oldest. This allows for exceptions to be handled from parent exceptions which were otherwise ignored.
- Fixed incorrect return type for InputFile::getError to nullable int.
- Added return type to all, match, controller and resource in SimpleRouter class.
- Fixed incorrect usage of parse_str in Url::setQueryString method.
- Fixed incorrect expected value in array_flip in Url::removeParams method.
2021-05-02 13:48:13 +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ø 569b3a8760 Merge pull request #541 from skipperbent/v4-development
Version 4.3.2.3
2021-04-28 10:05:34 +02:00
Simon Sessingø 79a075ef49 Merge pull request #540 from skipperbent/v4-type-hint-notice
Fixed deprecated notice when using class type hinting (issue: #538)
2021-04-28 10:03:55 +02:00
Simon Sessingø c66d7f7df7 Fixed deprecated notice when using class type hinting (issue: #538) 2021-04-28 09:57:47 +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ø c6d0ff3c0e Merge pull request #536 from skipperbent/v4-development
Version 4.3.2.2
2021-04-28 03:41:47 +02:00
Simon Sessingø 718d60c53b Removed return type from IResourceController as the return type can be mixed. 2021-04-04 11:21:26 +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ø 2a573f27fe Merge pull request #534 from skipperbent/v4-development
Version 4.3.2.1
2021-04-01 03:16:29 +02:00
Simon Sessingø ecbb0825e0 Added include param parameter to Url::getAbsoluteUrl method. 2021-04-01 03:14:22 +02:00
Simon Sessingø b94dc4355f Optimisations 2021-04-01 03:11:05 +02:00
Simon Sessingø 52c6c226c0 [BUGFIX] Fixed issue with BaseCsrfVerifier matching urls against urls with parameters.
- Added optional $includeParams parameter to Url::getRelativeUrl method.
2021-04-01 03:04:32 +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ø 982fb9fab4 Merge pull request #532 from skipperbent/v4-development
Version 4.3.2.0
2021-04-01 02:37:52 +02:00
Simon Sessingø ca8fbf2b27 Merge pull request #531 from skipperbent/v4-feature-ip
[FEATURE] IP restrict access
2021-04-01 02:35:40 +02:00
Simon Sessingø e4584a451d Improved phpDoc for prepend methods 2021-03-31 13:31:20 +02:00
Simon Sessingø 8b11377fe8 Fixed typo 2021-03-31 03:25:35 +02:00
Simon Sessingø eccda10169 Added prependPrefix to Group class & updated documentation. 2021-03-31 03:23:04 +02:00
Simon Sessingø d92d50ecdc Updated features in documentation 2021-03-31 03:08:01 +02:00
Simon Sessingø dca0389115 Merge branch 'v4-development' into v4-feature-ip 2021-03-31 03:00:16 +02:00
Simon Sessingø 7adb4e8597 Fixed wrong link for partial-groups 2021-03-31 02:40:24 +02:00
Simon Sessingø b0e4becbba Merge branch 'v4-development' of github.com:skipperbent/simple-php-router into v4-development 2021-03-31 02:36:48 +02:00
Simon Sessingø 3b8e92b406 Updated documentation table of contents 2021-03-31 02:36:11 +02:00
Simon Sessingø 0e393fdc5f Minor changes
- Added better description of partialGroups in the documentation.
- Added custom base path example in documentation.
- Added isSubRoute event parameter for EVENT_ADD_ROUTE.
- Removed deprecation phpDoc from partialGroup.
- Added unit-test for adding custom base path.
2021-03-31 02:31:56 +02:00
Simon Sessingø 56c73640b7 Merge pull request #530 from skipperbent/v4-feature-verifier
[FEATURE] Added include property to BaseCsrfVerifier + unit tests.
2021-03-31 01:02:05 +02:00
Simon Sessingø f91f280975 Added https scheme to Request::setUri (used when calling getAbsoluteUrl). 2021-03-30 21:13:06 +02:00
Simon Sessingø 40f9b72963 Updated documentation 2021-03-30 20:52:39 +02:00
Simon Sessingø 245b909ab6 Updated readme 2021-03-30 20:48:37 +02:00
Simon Sessingø 50b7129cab Changed name of IpBlockAccess to IpRestrictAccess & updated documentation. 2021-03-30 20:44:40 +02:00
Simon Sessingø 57047d23ea [FEATURE] Ip access block 2021-03-30 20:38:18 +02:00
Simon Sessingø a98b5ba842 Fixed unit-tests for CsrfVerifierTest 2021-03-30 18:54:45 +02:00
Simon Sessingø b3d28e9432 [FEATURE] Added include åproperty to BaseCsrfVerifier + unit tests. 2021-03-30 18:49:37 +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ø d0c34255b5 Merge pull request #528 from skipperbent/v4-development
Version 4.3.1.0
2021-03-29 22:31:01 +02:00
Simon Sessingø 5a917a6905 Merge pull request #526 from skipperbent/v4-php8-fix
Version 4.3.1.0-test
2021-03-29 22:28:44 +02:00
Simon Sessingø be2d45f0ad Updated documentation 2021-03-29 22:24:02 +02:00
Simon Sessingø dd9a6eab7d [FEATURE] Added class + method loading to IClassLoader. 2021-03-29 22:15:55 +02:00
Simon Sessingø 5621ffc724 Updated documentation 2021-03-29 22:04:46 +02:00
Simon Sessingø 438193ef59 Added deprecated warning for RoutePartialGroup. 2021-03-29 21:59:30 +02:00
Simon Sessingø adc879bb13 [BUGFIX] Fixed InputHandler::find and InputHandler::value failing when using array methods. 2021-03-29 18:45:49 +02:00
Simon Sessingø 06ee78a48f Updated example for group parameters 2021-03-29 17:41:18 +02:00
Simon Sessingø 4b992f0a2f Merge pull request #527 from skipperbent/v4-feature-group-parameters
[FEATURE] Added support for parameters in group prefix.
2021-03-29 17:14:51 +02:00
Simon Sessingø c423172c23 Added deep parameters pass unit-test 2021-03-29 17:10:12 +02:00
Simon Sessingø d6d83ac5bd Parameters are now correctly passed on to sub-routes 2021-03-29 17:05:45 +02:00
Simon Sessingø b05bbccc28 [FEATURE] Added support for parameters in group prefix. 2021-03-29 16:56:37 +02:00
Simon Sessingø d6bc713e5b [CLEANUP] Added qualifier import. 2021-03-29 15:40:50 +02:00
Simon Sessingø 8eba5ab3d5 [FEATURE] php8 compatibility.
- Fixed possible error causing parameters not to be set properly when using partialGroup.
- Removed unused import reference.
- Added unit-tests.
2021-03-29 15:11:58 +02:00
Simon Sessingø 8c5ed8410a Merge pull request #525 from skipperbent/v4-release
V4 release
2021-03-29 01:19:40 +02:00
Simon Sessingø c4cf878e97 Merge pull request #494 from skipperbent/v4-development
Version 4.3.0.0
2021-03-29 01:19:02 +02:00
Simon Sessingø 5b99e98a24 Merge pull request #524 from skipperbent/v4-cleanup
[CLEANUP] Cleaned up code
2021-03-29 00:07:12 +02:00
Simon Sessingø c916a1dd2e [CLEANUP] Cleaned up code
- Change variable $values to $settings in Route::setSettings method.
- Added return types to methods.
- Added type to method variables.
- Change ClassNotFoundException so required parameters are first.
2021-03-29 00:00:01 +02:00
Simon Sessingø 9ed2d2b8d1 Updated Request::isPostBack to return true if request-method could contain data in body. 2021-03-28 23:32:33 +02:00
Simon Sessingø caf30cb056 Merge pull request #523 from skipperbent/v4-isPostBack
[FEATURE] Added Request::isPostBack helper method
2021-03-28 04:25:54 +02:00
Simon Sessingø 6ccd06911e Fixed possible bug causing InputHandler not to get the correct request-method + simplified Request class. 2021-03-28 04:24:33 +02:00
Simon Sessingø e5eb966780 Added Request::isPostBack helper method 2021-03-28 04:05:09 +02:00
Simon Sessingø 9e19cbfb71 Merge pull request #521 from skipperbent/v4-fix-group-domain
[BUGFIX] Fixed group not matching domain with no parameters (issue: #468)
2021-03-26 01:35:37 +01:00
Simon Sessingø 073479f9dd [BUGFIX] Fixed group not matching domain with no parameters (issue: #468).
- Added unit-tests
2021-03-26 01:32:01 +01:00
Simon Sessingø 39ee1bb7cd Added missing phpDocs for parameters. 2021-03-25 14:31:04 +01:00
Simon Sessingø cf1c59aee0 Changed parameter name and added parameter phpDocs description. 2021-03-25 14:24:50 +01:00
Simon Sessingø d9cfa71534 Fixed naming in unit-tests. 2021-03-25 14:21:30 +01:00
Simon Sessingø cf6750aaf3 [FEATURE] Optimised Request::getIp method
- Added unit-tests for Request::getIp
- Optimised existing RequestTest unit-tests.
2021-03-25 14:18:56 +01:00
Simon Sessingø 11313a31dc Merge pull request #504 from DeveloperMarius/get-ip-proxy
Get the ip when the user is using a proxy
2021-03-25 14:17:55 +01:00
DeveloperMarius 4cb2fa521f v4-development sync 2021-03-25 13:41:04 +01:00
Simon Sessingø 8835aca02e [FEATURE] Added Request::getContentType for content-type header-parsing
- Added unit-tests for Request::getContentType parsing.
2021-03-25 13:09:23 +01:00
Simon Sessingø 86bb88a41f [FEATURE] Added better support for nested file/arrays in InputHandler.
- Added unit tests for file arrays
- Removed legacy .yml configs
2021-03-25 05:58:49 +01:00
Simon Sessingø fdf11bbc9c Merge pull request #517 from DeveloperMarius/file-tests
PHPUnit file tests
2021-03-25 04:34:29 +01:00
Simon Sessingø 2b9403db28 Features & bugfixes
- Feature: added new getFirstHeader to Request object that will return the first header found from array list- used to simplify the code.
- Feature: added new InputHandler::getValueFromArray method that loops through input-items to ensure that value is always returned.
- Fixed calling getUrl with array as parameters option throws error.
- Fixed `SimpleRouter::getUrl` having wrong nullable return type.
2021-03-25 03:41:11 +01:00
Simon Sessingø 0ec7c0d960 Optimisations
- Added phpunit cache to .gitignore
- Updated README with latest helper.php example.
- Minor phpDocs changes.
2021-03-24 23:18:17 +01:00
Simon Sessingø d9c63699f5 Merge pull request #516 from DeveloperMarius/phpunit-fix
[IMPORTANT] PHPUnit fix: Element 'coverage': This element is not expected.
2021-03-24 14:03:58 +01:00
DeveloperMarius 3e1333ccd4 Defined variables for the file data 2021-03-24 13:32:15 +01:00
DeveloperMarius fef65313e5 Added file content test for all platforms 2021-03-24 13:20:41 +01:00
DeveloperMarius 5624c4b2bb testFiles and test file content on server 2021-03-24 13:11:07 +01:00
DeveloperMarius 543e550daf PHPUnit fix: Element 'coverage': This element is not expected. 2021-03-24 11:48:29 +01:00
Simon Sessingø 22d531178a Merge pull request #512 from DeveloperMarius/github-actions
GitHub actions
2021-03-23 15:57:03 +01:00
DeveloperMarius 0892f5b6f3 replaced php version 7.3 with with 7.1 2021-03-23 15:49:32 +01:00
DeveloperMarius 1d48034910 code cleanup 2021-03-23 15:48:36 +01:00
DeveloperMarius 61fee760b0 use phpunit version 7.5.20 like composer 2021-03-23 15:46:22 +01:00
DeveloperMarius cfc8b5db43 Test PHP Version 7.1 with PHPUnit 7 2021-03-23 15:44:43 +01:00
DeveloperMarius 619a8d00b4 v4-development sync 2021-03-23 15:42:44 +01:00
Simon Sessingø 42633ec453 php-unit updates 2021-03-23 15:38:10 +01:00
Simon Sessingø dbd8d381e7 Updated travis.yml 2021-03-23 15:31:47 +01:00
Simon Sessingø 57936b7857 Reverted back php-unit version 2021-03-23 15:26:57 +01:00
Simon Sessingø c466af556e Updated travis.yml 2021-03-23 15:22:31 +01:00
Simon Sessingø db63aff668 Updated php-unit to version 9 2021-03-23 15:20:05 +01:00
Simon Sessingø df52ec3df7 Updated travis.yml 2021-03-23 15:15:58 +01:00
Simon Sessingø 7920188956 Updated travis.yml 2021-03-23 15:11:44 +01:00
Simon Sessingø 635b127357 Fixed correct return type for InputFile. 2021-03-23 15:06:21 +01:00
Simon Sessingø b9af44299e Merge pull request #514 from skipperbent/v4-inputitem-value
[FEATURE] Add support for mixed value types in InputItem as requested by #438
2021-03-23 15:02:18 +01:00
Simon Sessingø a070af2145 Merge pull request #510 from skipperbent/v4-disable-multi-routing
[FEATURE] Option to disable multi-route-rendering #452
2021-03-23 15:00:14 +01:00
Simon Sessingø a33f2f7e7a Merge branch 'v4-development' into v4-disable-multi-routing 2021-03-23 15:00:01 +01:00
Simon Sessingø 2689486e64 Merge pull request #509 from skipperbent/v4-request-same-routes
[BUGFIX] Issue #439: Fixed multiple request-type on same routes.
2021-03-23 14:57:17 +01:00
Simon Sessingø dfc12d07b0 Merge pull request #508 from skipperbent/v4-bootmanager-fix
[BUGFIX] BootManager findRoute not working.
2021-03-23 14:56:16 +01:00
Simon Sessingø 3ebe1a8af2 Merge pull request #507 from skipperbent/feature-default-namespace
[FEATURE] Default-namespace optimisations #446
2021-03-23 14:53:32 +01:00
Simon Sessingø 680e0256c3 Merge branch 'v4-development' into feature-default-namespace 2021-03-23 14:52:39 +01:00
Simon Sessingø a8068f76a3 Merge pull request #513 from skipperbent/v4-phpdi-remove
[!!!][FEATURE] Removed php-di as suggested by #477
2021-03-23 14:50:19 +01:00
Simon Sessingø 7ba864420e Updated travis.yml 2021-03-23 14:47:11 +01:00
Simon Sessingø 8670af356b Updated travis.yml 2021-03-23 14:37:04 +01:00
Simon Sessingø 6be9d1003c Updated travis.xml 2021-03-23 14:33:25 +01:00
DeveloperMarius 5095b1abc9 cleanup 2021-03-23 13:54:00 +01:00
DeveloperMarius 5275653606 Correct test to succeed 2021-03-23 13:41:27 +01:00
DeveloperMarius 62f5e5cbbd Add Failing test 2021-03-23 13:40:22 +01:00
DeveloperMarius 08008ca847 Add PHP 7.3 check 2021-03-23 13:40:08 +01:00
DeveloperMarius 9f0373938d fix composer test command 2021-03-23 13:35:57 +01:00
DeveloperMarius c408f79d8a changed to PHP >= 3 & run test via composer test 2021-03-23 13:34:55 +01:00
DeveloperMarius 69fdfb3560 remove matrix include & PHPUnit requires PHP >= 7.3 2021-03-23 13:30:57 +01:00
DeveloperMarius a103c71163 Try to fix composer install missing 2021-03-23 13:28:17 +01:00
DeveloperMarius d3000775d6 correct composer exec path 2021-03-23 13:27:18 +01:00
DeveloperMarius 824ee86652 Try to fix composer install missing 2021-03-23 13:25:44 +01:00
DeveloperMarius 495cfba613 Added relative path 2021-03-23 13:22:32 +01:00
DeveloperMarius 5eadb79c64 add phpunit tool 2021-03-23 13:20:29 +01:00
DeveloperMarius 4725b330fe change test command & phpunit file update 2021-03-23 13:19:36 +01:00
DeveloperMarius 537d607b9f Try to fix caching bugg 2021-03-23 13:17:07 +01:00
DeveloperMarius 9029a84fdd reduced platforms and version for test & added composer 2021-03-23 13:04:59 +01:00
DeveloperMarius 87985841de complete change of tests 2021-03-23 12:48:49 +01:00
DeveloperMarius 37228d2bac revert change 2021-03-23 12:47:24 +01:00
DeveloperMarius 26a1659734 Try to fix failures 2021-03-23 12:07:53 +01:00
DeveloperMarius 554d562e56 Seem to get no error local, test on actions 2021-03-23 12:04:10 +01:00
DeveloperMarius 577c87c527 Try to run phpunit from composer 2021-03-23 11:57:03 +01:00
DeveloperMarius d7a295cb5c Change php version and test execution 2021-03-23 11:53:29 +01:00
Marius Karstedt 656946fbb2 Used cmd instead of run 2021-03-23 03:33:51 +01:00
Marius Karstedt aa8211a273 added tests to composer 2021-03-23 03:23:18 +01:00
Simon Sessingø 90b0747dbd [BUGFIX] Add support for mixed value types in InputItem as requested by #438 2021-03-23 01:36:23 +01:00
Simon Sessingø e721a92156 Updated readme 2021-03-23 01:25:57 +01:00
Simon Sessingø 67211e5332 Updated readme + gitignore 2021-03-23 01:24:17 +01:00
Simon Sessingø a44a93d705 [!!!][FEATURE] Removed php-di as suggested by #477
NOTE: Custom class-loader should be used to create custom integrations with frameworks like php-di. See documentation for more information.

- Removed all references to php-cli from composer + code.
- Added ClassLoader php-unit tests.
2021-03-23 00:46:17 +01:00
DeveloperMarius 4adfa4f322 Rollback to warning: 'Suggestion: Migrate your XML configuration using --migrate-configuration!' 2021-03-23 00:18:14 +01:00
DeveloperMarius f9c0c83b70 migrate configuration 2021-03-23 00:09:45 +01:00
DeveloperMarius eebd537749 removed syntaxCheck (throws error) https://stackoverflow.com/questions/44328114/phpunit-what-does-syntaxcheck-configuration-parameter-stands-for-exactly/44331140#44331140 2021-03-22 23:55:11 +01:00
DeveloperMarius 0e58d556f0 Maby have to add args? 2021-03-22 23:45:37 +01:00
DeveloperMarius 791ba3199d Error says phpunit needs PHP >= 7.3, let's try it 2021-03-22 23:38:23 +01:00
DeveloperMarius 3c8740769a No try to add unit tests 2021-03-22 23:36:32 +01:00
DeveloperMarius 029739f241 Just install composer without tests 2021-03-22 23:31:39 +01:00
DeveloperMarius c67c6759a8 Try without unit tests 2021-03-22 23:28:16 +01:00
DeveloperMarius 21710c083c Trying to fix docker build error 2021-03-22 23:19:18 +01:00
DeveloperMarius 7013822358 changed bootstrap file 2021-03-22 23:13:05 +01:00
DeveloperMarius deb6922d0c change php version to 7.1 2021-03-22 23:10:20 +01:00
DeveloperMarius cb2cb91a0a Add on pull request 2021-03-22 23:00:39 +01:00
DeveloperMarius 533dd08217 Github Actions CI on push 2021-03-22 22:58:53 +01:00
DeveloperMarius 5508c73e85 getIp() update to new header method 2021-03-22 22:32:44 +01:00
DeveloperMarius d5dc81e26e v4-development sync & getIp() update to new header method 2021-03-22 22:25:37 +01:00
Simon Sessingø 6686de46b9 Updated link to simple-router demo. 2021-03-22 20:35:42 +01:00
Simon Sessingø 5c8ff17aec Updated documentation for information about multi-route-rendering. 2021-03-22 19:43:13 +01:00
Simon Sessingø 1d2e5f47d9 [FEATURE] Option to disable multi-route rendering
- Added option to disable multi-route rendering by calling `Router::setRenderMultipleRoutes($bool)`.
- Added alias for easier access `SimpleRouter::enableMultiRouteRendering($bool)`.
- Added php-unit tests for multi-routing enabled and disabled.
2021-03-22 19:34:55 +01:00
Simon Sessingø f74252e8cc Removed newline 2021-03-22 18:34:14 +01:00
Simon Sessingø 2fb59854be [BUGFIX] Issue #439: Fixed multiple request-type on same routes. 2021-03-22 18:33:16 +01:00
Simon Sessingø 801f1e68cc [BUGFIX] BootManager findRoute not working.
- Fixed findRoute not working in BootManager as reported by issue: #448
- Added more comprehensive php-unit tests for bootmanagers including findUrl.
2021-03-22 18:05:27 +01:00
Simon Sessingø fa83d2f74b Default-namespace changes.
- Added new ClassNotFoundHttpException thrown when class is not found.
- ClassNotFoundHttpException is now thrown when class/method is not found (backwards compatible).
- Added unit-tests for default-namespace tests (rewrite + append cases).
2021-03-22 17:03:22 +01:00
Simon Sessingø fd585e8b9d [FEATURE] Changed behavior for default-namespace after issue #446
- Router::setDefaultNamespace() no longer has to be set in the beginning of routes.php.
- Default namespace are now set once the router is started (Router::start()).
- [WIP] Added unit-tests for custom-namespaces.
2021-03-22 16:41:20 +01:00
Simon Sessingø 11fffd9a7b [FEATURE] Added option to get/set the filterEmptyParams option on IRoute classes (as requested by: #453). 2021-03-22 15:06:20 +01:00
Simon Sessingø 87e9c19edb Added unit-tests for input->all() method. 2021-03-22 14:49:33 +01:00
Simon Sessingø 8a0f30c05e Custom regex fix
- Fixed issue with custom-regex maching both host-name and url (issue: #503).
- Changed TestRouter so host-name is always set.
2021-03-22 14:09:27 +01:00
Marius Karstedt 1e0417b249 also check remote-addr (can be edited https://stackoverflow.com/questions/5092563/how-to-fake-serverremote-addr-variable) 2021-03-22 11:18:16 +01:00
Marius Karstedt 24f7e3ab13 Validate IP header 2021-03-22 11:15:47 +01:00
Simon Sessingø d2b3ea4f54 Added better header parsing to Request-class.
- Added `tryParse` argument to the `getHeader` method. When enabled the method will try to parse headers from both server and client-side (enabled by default).
- Simplified references that checks for both variants of header (http/non http).
- Simplified getIp method of the Request-class.
2021-03-22 11:14:22 +01:00
Marius Karstedt 90a0ca2ee8 Add cf ip header to none save call 2021-03-22 11:06:33 +01:00
Marius Karstedt 9897f66a25 Add $safe 2021-03-22 11:04:33 +01:00
Marius Karstedt e77b723db3 Add http-client-ip header 2021-03-22 10:57:58 +01:00
Simon Sessingø 0aeefa1cba Removed ob_end_clean when using ob_get_clean. 2021-03-21 15:22:35 +01:00
Simon Sessingø 8254c5b100 Development
- Removed unused class references.
- Removed escape from `-` in reg-ex as it's only required when next to character-class.
- Added `$_FILE` support for `all` method.
- Bugfixes.
2021-03-21 15:19:27 +01:00
Simon Sessingø 4639879a67 Merge pull request #502 from skipperbent/DeveloperMarius-get-csrf-token
Developer marius get csrf token
2021-03-21 14:59:08 +01:00
Simon Sessingø 14fe889298 Merge branch 'v4-development' into DeveloperMarius-get-csrf-token 2021-03-21 14:58:20 +01:00
Simon Sessingø dfcbbb4033 Merge pull request #501 from DeveloperMarius/get-csrf-token
get csrf token in request and test for prefix 'http-' in csrf token header
2021-03-21 14:57:10 +01:00
Simon Sessingø e8a1eac167 Development
- Moved request-types constants from abstract Route class to global Request-class and changed references.
- Changed code to use new global request-type constants.
- Optimized InputHandler class so it only parses inputs once when calling all-method.
- Forced csrf-token post-value are now availible in all requestTypePost methods.
2021-03-21 14:52:34 +01:00
Simon Sessingø 2ff278baef Merge branch 'get-csrf-token' of https://github.com/DeveloperMarius/simple-php-router into DeveloperMarius-get-csrf-token 2021-03-21 14:16:20 +01:00
Simon Sessingø 388be3d870 Merge pull request #500 from DeveloperMarius/remove-idea-config
remove .idea files
2021-03-21 14:03:05 +01:00
Simon Sessingø f45e0bd12a Removed .idea folder and removed "index" output from the unit-tests. 2021-03-21 13:59:35 +01:00
Marius Karstedt 31b4b4673e add csrf token check for patch 2021-03-21 12:20:57 +01:00
Marius Karstedt 05e5461acb get csrf token in request; Test for prefix 'http-' in csrf token header 2021-03-21 11:40:37 +01:00
Marius Karstedt 3970ad85c4 remove .idea files 2021-03-21 10:47:49 +01:00
Simon Sessingø 38ce2e6bba Unit test fixes 2021-03-21 08:02:41 +01:00
Simon Sessingø 2306ab47db Merge pull request #499 from skipperbent/fix-partial-group
[BUGFIX] Fixed issue with child groups not loading when using partialGroups (issue: #456)
2021-03-21 07:57:14 +01:00
Simon Sessingø 4674dbef1a Merge branch 'v4-development' into fix-partial-group 2021-03-21 07:57:01 +01:00
Simon Sessingø f50ed6cd27 Merge pull request #498 from skipperbent/v4-array-callback
[FEATURE] Added support for class hinting on routes as requested by #491
2021-03-21 07:55:27 +01:00
Simon Sessingø d70b153189 [BUGFIX] Fixed issue with child groups not loading when using partialGroups (issue: #456) 2021-03-21 07:39:17 +01:00
Simon Sessingø 21d180ebc9 [FEATURE] Added support for class hinting on routes as requested by #491 2021-03-21 05:55:18 +01:00
Simon Sessingø e78040aabd Fixed existing unit-tests. 2021-03-18 21:58:40 +01:00
Simon Sessingø 252cc4a75d Optimized InputHandler to better support nested values. 2021-03-18 21:58:12 +01:00
Simon Sessingø 24ef438334 Merge pull request #495 from SimonSchobel/patch-1
Fixed bug in getIp
2021-03-18 14:03:39 +01:00
Simon Schøbel 6fc0241bab Fixed bug in getIp
Fixed typo
2021-03-18 08:12:48 +01:00
Simon Sessingø 19b1a14dec Parameters are by default now using regex [\w\-]+ (supports dashes) to avoid confusion. 2021-03-18 03:24:47 +01:00
Simon Sessingø fb726c3613 Issue #437: Fixed CSRF-token returning null on first refresh after cookies are removed. 2021-03-18 02:50:06 +01:00
Simon Sessingø 11a69c2f72 Fixed user-tests after changes to input->all() method. 2021-03-17 20:26:07 +01:00
Simon Sessingø ff8ef9d412 Merge branch 'v4-development' of github.com:skipperbent/simple-php-router into v4-development 2021-03-17 20:20:56 +01:00
Simon Sessingø ca88e86c3d Development
- Removed unused exception from PHP-docs.
- Fixed types not same as declared.
- Fixed issues with reg-ex and php-unit tests.
- Removed unnecessary type casting.
- Declared functions as static (better scoping + performance).
- Moved `\is_callable($callback) === false` as the execution costs less than previous in `Router.php`.
- Changed `ob_get_contents` to `ob_get_clean`.
- Added type hints to methods.
2021-03-17 20:20:28 +01:00
Simon Sessingø 5a24dfd4a1 Merge pull request #461 from b3none/master
Alter behaviour of input()->all when a filter is passed.
2021-03-17 19:25:51 +01:00
Simon Sessingø 891c2092eb Changed codestyle to match the rest 2021-03-17 19:25:18 +01:00
Simon Sessingø 845ef9db69 Merge pull request #450 from RedooNetworks/master
Fix warning, because of wrong calculated cookie expiration timestamp
2021-03-17 19:17:01 +01:00
Simon Sessingø 00c9f9cafd Update composer.json 2021-03-17 19:16:08 +01:00
Simon Sessingø 0fe2733c85 Merge pull request #445 from KarelWintersky/patch-1
Update README.md
2021-03-17 19:14:15 +01:00
Simon Sessingø ce3a2014d1 Merge pull request #449 from jatubio/patch-4
Fix 'must be an instance of Closure, array given' error when $closure is a object method.
2021-03-17 19:12:00 +01:00
Simon Sessingø 27cd8b8a1f Added support for objects like array etc as default-value. Value is now less strict. 2021-03-17 15:43:00 +01:00
Alex Blackham 93c0622b9d Altered variable name in foreach to be less ambiguous. 2019-06-21 10:45:57 +01:00
Alex Blackham 572ba1695b The input()->all method will now set every key specified in the filter. If the key doesn't exist it will be set to null. 2019-06-21 10:44:31 +01:00
Stefan Warnat 1c5701a297 Update compsoer.json 2019-05-19 11:02:09 +02:00
Stefan Warnat 11df7ca18c Fix warning, because of wrong calculated cookie expiration timestamp 2019-02-09 11:04:07 +01:00
Juan Antonio Tubio b21feca1fc Update IClassLoader.php
Fix 'must be an instance of Closure, array given' error when $closure is a object method.
2019-02-08 21:40:04 +01:00
Juan Antonio Tubio 2a3238f30a Update ClassLoader.php
Fix 'must be an instance of Closure, array given' error when $closure is a object method.
2019-02-08 21:36:58 +01:00
Karel Wintersky cb141314f7 Update README.md
fix https://github.com/skipperbent/simple-php-router/issues/444
2019-01-28 17:43:11 +03:00
Simon Sessingø 153f8630f2 Merge pull request #435 from skipperbent/v4-release
V4 release
2018-11-25 00:47:26 +01:00
Simon Sessingø b715c48415 Merge pull request #434 from skipperbent/v4-development
Version 4.2.0.6
2018-11-25 00:47:02 +01:00
Simon Sessingø d601e8eca3 Bugfixes and optimisations.
- Fix for __invoke methods (issue: #429)
- Fixed not being able to parse body of PUT request.
- BaseCsrfVerifier expects the field name to be "csrf-token" (issue: #432)
- Minor optimisations
2018-11-25 00:44:20 +01:00
Simon Sessingø 4a8b71a0b5 Merge pull request #430 from Tahrz/master
fix for __invoke methods (if there are only in class (Action))
2018-11-25 00:27:54 +01:00
Simon Sessingø bd01a8a802 Merge pull request #431 from matheusheiden/master
Set HTTP method before initializing InputHandler
2018-11-25 00:27:30 +01:00
Matheus 55bcd4e030 Merge pull request #1 from matheusheiden/matheusheiden-patch-1
Set HTTP method before initializing InputHandler
2018-11-10 22:17:08 -02:00
Matheus d4a6d504b1 Set HTTP method before initializing InputHandler
This is needed because without it the InputHandler doesn't know which http method is being used, and because of that it can't parse the body of a PUT request
2018-11-10 22:16:45 -02:00
Taras Victorovich 9e3b1b6baa fix for __invoke methods (if there are only in class (Action)) 2018-10-02 15:40:26 +03:00
Simon Sessingø 1395527cc6 Merge pull request #428 from skipperbent/v4-release
V4 release
2018-08-31 01:41:48 +02:00
Simon Sessingø ac594ebde2 Merge pull request #427 from skipperbent/v4-development
Version 4.2.0.5
2018-08-31 01:41:36 +02:00
Simon Sessingø 064154f27f Merge pull request #426 from skipperbent/v4-optimisations
Optimizations
2018-08-31 01:41:05 +02:00
Simon Sessingø 0ac7fd559a Optimizations
- Settings parameter in group method are no longer optional.
- Updated README to contain PHP JSON-extension under requirements.
- Updated composer.json to include php json extension.
2018-08-31 01:40:21 +02:00
Simon Sessingø da8dfdb135 Merge pull request #424 from skipperbent/v4-release
V4 release
2018-08-24 17:24:48 +02:00
Simon Sessingø 57c9da2c42 Merge pull request #423 from skipperbent/v4-development
Version 4.2.0.4
2018-08-24 17:24:25 +02:00
Simon Sessingø 6bdfe06223 Merge pull request #422 from skipperbent/v4-optional-character-fix
Fix for issue #421: Incorrectly optional character in route
2018-08-24 17:23:43 +02:00
Simon Sessingø 5c2a973214 Merge branch 'v4-development' into v4-optional-character-fix 2018-08-24 17:23:33 +02:00
Simon Sessingø a7cbacbde7 Fix for issue #421: Incorrectly optional character in route 2018-08-24 17:16:02 +02:00
Simon Sessingø f0c851e49d Merge pull request #417 from skipperbent/v4-release
V4 release
2018-04-22 05:37:41 +02:00
Simon Sessingø 45a5176b3c Merge pull request #416 from skipperbent/v4-development
Version 4.2.0.3
2018-04-22 05:37:29 +02:00
Simon Sessingø 2d042391aa Merge pull request #415 from skipperbent/v4-fix-input-all-empty
Fixed returning empty array when positing json (issue #413)
2018-04-22 05:36:10 +02:00
Simon Sessingo 4d842d0583 Fixed returning empty array when positing json (issue #413) 2018-04-22 05:35:15 +02:00
Simon Sessingø 7c54d319e6 Merge pull request #411 from skipperbent/v4-release
V4 release
2018-04-06 19:46:01 +02:00
Simon Sessingø 9ccff91287 Merge pull request #410 from skipperbent/v4-development
Version 4.2.0.2
2018-04-06 19:45:51 +02:00
Simon Sessingø 8653bfa86f Development
- Fixed 403 not allowed exception is now thrown as NotFoundHttpException.
- Added REQUEST_TYPE_HEAD to Route class.
- Minor optimizations.
2018-04-06 19:44:30 +02:00
Simon Sessingø e51290dfd8 Merge pull request #409 from skipperbent/v4-release
V4 release
2018-04-06 18:04:28 +02:00
Simon Sessingø 13501b3f88 Merge pull request #408 from skipperbent/v4-development
Version 4.2.0.1
2018-04-06 18:04:01 +02:00
Simon Sessingø b8cfc4eb0b Fixed default parameter for all method allowed to by empty. 2018-04-06 18:01:49 +02:00
Simon Sessingø 5db4621831 Added methods for adding get, post and file parameters manually. 2018-04-06 18:00:59 +02:00
Simon Sessingø d4bbb6641d Merge pull request #407 from skipperbent/v4-release
V4 release
2018-04-06 17:28:12 +02:00
Simon Sessingø af641e3805 Merge pull request #406 from skipperbent/v4-development
Version 4.2.0.0
2018-04-06 17:27:42 +02:00
Simon Sessingø d38f81836d Development
- Added new Redirect method to SimpleRouter class.
- Changed method-names in InputHandler for better description.
- Fixed return-types for InputHandler for collections.
- Added unit-tests for InputHandler (get, post).
- Optimisations.
2018-04-06 17:20:00 +02:00
Simon Sessingø ca1e2ef94b Merge pull request #404 from skipperbent/v4-release
V4 release
2018-04-05 12:19:27 +02:00
Simon Sessingø 89be00a72a Merge pull request #402 from skipperbent/v4-development
Version 4.1.0.0
2018-04-02 14:56:12 +02:00
Simon Sessingø 30a2ddeed9 Development
- Added new event when adding route.
- Added `prependUrl` method to `LoadableRoute` class.
- Added unit-test for add-route event.
- Updated documentation to reflect new changes.
2018-04-02 14:53:36 +02:00
Simon Sessingø 313833d78a Merge pull request #400 from skipperbent/v4-development
Fixed `exists` method in `InputHandler` returning incorrect value.
2018-04-01 04:28:06 +02:00
Simon Sessingø 17a7b28e82 Fixed exists method in InputHandler returning incorrect value. 2018-04-01 04:26:48 +02:00
Simon Sessingø e77d78e2f2 Merge pull request #399 from skipperbent/v4-development
Bugfixes and optimizations
2018-04-01 03:02:49 +02:00
Simon Sessingø 1dc88d23e1 Bugfixes and optimizations
- Fixed `hasParam` not working returning expected value in `Url` class.
- Chained the remaining methods in the `Url` class.
- Simplified `removeParam` method in `Url` class.
- Added new `removeParams` method to `Url` class for removal of multiple params.
2018-04-01 03:02:21 +02:00
Simon Sessingø bd033d9e13 Merge pull request #398 from skipperbent/v4-development
Version 4.0.0.11
2018-03-30 06:48:15 +02:00
Simon Sessingø 53f0b7d8e2 - Fixed getName method in LoadableRoute class can contain nullable value. 2018-03-30 06:47:27 +02:00
Simon Sessingø 5df0c12864 Updated helpers 2018-03-30 05:54:12 +02:00
Simon Sessingø 5bae3ff773 Merge pull request #397 from skipperbent/v4-development
Version 4.0.0.10
2018-03-30 05:38:27 +02:00
Simon Sessingø 833961ddc3 Fixed getError in InputFile returning string instead of int. 2018-03-30 05:37:04 +02:00
Simon Sessingø 36388f0f79 Merge pull request #396 from skipperbent/v4-development
Version 4.0.0.9
2018-03-30 05:23:54 +02:00
Simon Sessingø ce63e247b1 Bugfixes
- Fixed `Url` not outputting correct class when used in json_encode.
- Fixed `IInputItem` being too strict about strings which may be nullable.
2018-03-30 05:22:41 +02:00
Simon Sessingø d2d3938bf4 Merge pull request #395 from skipperbent/v4-development
Bugfixes
2018-03-29 23:48:32 +02:00
Simon Sessingø 80a42030ea Bugfixes
- Fixed getting specific input-value by request-method in InputHandler.
- Added .idea files
2018-03-29 23:47:27 +02:00
Simon Sessingø 1d6a2fafff Merge pull request #394 from skipperbent/v4-development
Removed namespace from groups
2018-03-29 23:28:47 +02:00
Simon Sessingø 17471a53cd Removed namespace from groups 2018-03-29 23:28:03 +02:00
Simon Sessingø e97e624cef Merge pull request #393 from skipperbent/v4-development
Bugfixes
2018-03-29 23:14:22 +02:00
Simon Sessingø da219d0b19 Bugfixes
- Fixed rewrite from ExceptionHandler sometimes not working correctly.
- Fixed default-namespace for Group and partial groups.
2018-03-29 23:13:56 +02:00
Simon Sessingø 0fc40f2a82 Merge pull request #384 from skipperbent/v3
V3
2018-03-03 23:44:32 +01:00
Simon Sessingø c5c63671ef Merge pull request #383 from skipperbent/v3-development
Development
2018-03-03 23:44:20 +01:00
Simon Sessingø 781fab48cc Merge pull request #382 from skipperbent/v3
V3
2018-02-27 09:05:08 +01:00
Simon Sessingø 6e247f811f Merge pull request #381 from skipperbent/v3-development
Throw correct exception-types.
2018-02-27 09:04:57 +01:00
Simon Sessingø 486d7c3a5b Merge pull request #380 from skipperbent/v3
Merge pull request #378 from skipperbent/v3-development
2018-02-27 08:37:14 +01:00
Simon Sessingø 559a65859e Merge pull request #379 from skipperbent/v3-development
Fixed issue with PDO exception not returning correct type for error-c…
2018-02-27 08:36:57 +01:00
Simon Sessingø 8111de48fd Merge pull request #378 from skipperbent/v3-development
Fixed issue with PDO exception not returning correct type for error-code.
2018-02-27 08:36:43 +01:00
Simon Sessingø 8b46d97418 Merge pull request #377 from skipperbent/v3
V3
2018-02-27 00:20:25 +01:00
Simon Sessingø 0dbc4e6ba2 Merge pull request #376 from skipperbent/v3-development
Optimisations
2018-02-27 00:20:12 +01:00
Simon Sessingø 38910ea9b6 Merge pull request #375 from skipperbent/v3
V3
2018-02-27 00:14:39 +01:00
Simon Sessingø a4dfa59a66 Merge pull request #374 from skipperbent/v3-development
Stop router from processing routes if no valid route is found.
2018-02-27 00:14:24 +01:00
Simon Sessingø d40eaba3e7 Merge pull request #373 from skipperbent/v3
V3
2018-02-26 23:49:41 +01:00
Simon Sessingø 2c9d996437 Merge pull request #372 from skipperbent/v3-development
Fixed setUrl issue.
2018-02-26 23:48:45 +01:00
Simon Sessingø 8b6a6864a7 Merge pull request #371 from skipperbent/v3
V3
2018-02-26 23:24:50 +01:00
Simon Sessingø 98ce5f7635 Merge pull request #370 from skipperbent/v3-development
Development
2018-02-26 23:24:37 +01:00
Simon Sessingø 6b22632141 Merge pull request #369 from skipperbent/v3
V3
2018-02-24 05:39:30 +01:00
Simon Sessingø 69bb570c73 Merge pull request #368 from skipperbent/v3-development
Version 3.5.0.0
2018-02-24 05:39:20 +01:00
Simon Sessingø ed320e2f87 Merge pull request #366 from skipperbent/v3
V3
2018-02-19 23:07:13 +01:00
Simon Sessingø c80b23e9d4 Merge pull request #365 from skipperbent/v3-development
Version 3.4.11.2
2018-02-19 23:07:02 +01:00
Simon Sessingø 76589e9d92 Merge pull request #364 from skipperbent/v3
Merge pull request #363 from skipperbent/master
2018-02-19 16:37:35 +01:00
Simon Sessingø 9d88bf10d2 Merge pull request #363 from skipperbent/master
Sync
2018-02-19 16:37:04 +01:00
Simon Sessingø dfa4a9aa6c Merge pull request #362 from skipperbent/v3-development
Version 3.4.11.1
2018-02-19 16:36:27 +01:00
Simon Sessingø 7c789ea0f8 Merge pull request #361 from skipperbent/v3
V3
2018-02-14 12:39:31 +01:00
Simon Sessingø 3ba9dd01f0 Merge pull request #360 from skipperbent/v3-development
Version 3.4.11.0
2018-02-14 12:39:19 +01:00
Simon Sessingø 14360b6779 Merge pull request #353 from skipperbent/v3
V3
2018-01-14 15:54:49 +01:00
Simon Sessingø bbb81333b4 Merge pull request #352 from skipperbent/v3-development
Fixed Input::all when php://input is not available
2018-01-14 15:54:37 +01:00
Simon Sessingø 161fbf6ccf Merge pull request #351 from skipperbent/v3
V3
2018-01-06 03:33:23 +01:00
Simon Sessingø 9a7b598880 Merge pull request #350 from skipperbent/v3-development
Version 3.4.10.0
2018-01-06 03:33:13 +01:00
Simon Sessingø 1486095e9f Merge pull request #345 from skipperbent/v3
V3 sync
2017-12-17 10:41:58 +01:00
Simon Sessingø 2db20dce6b Merge pull request #344 from skipperbent/v3-development
Version 3.4.9.3
2017-12-17 10:41:22 +01:00
Simon Sessingø 7cb416cfc8 Merge pull request #342 from skipperbent/v3
V3
2017-12-17 09:53:49 +01:00
Simon Sessingø c6341960d7 Merge pull request #341 from skipperbent/v3-development
Version 3.4.9.2
2017-12-17 09:53:39 +01:00
Simon Sessingø a570322e25 Merge pull request #340 from skipperbent/v3
V3
2017-12-16 12:54:06 +01:00
Simon Sessingø 85cf925793 Merge pull request #339 from skipperbent/v3-development
Version 3.4.9.1
2017-12-16 12:53:56 +01:00
Simon Sessingø feb6c8bd41 Merge pull request #337 from skipperbent/v3
V3
2017-12-09 23:58:48 +01:00
Simon Sessingø 7c0a20115e Merge pull request #336 from skipperbent/v3-development
Version 3.4.9.0
2017-12-09 23:58:33 +01:00
Simon Sessingø 276f213ccc Merge pull request #335 from skipperbent/v3
V3
2017-12-06 19:06:20 +01:00
Simon Sessingø 877e0aa937 Merge pull request #334 from skipperbent/v3-development
Version 3.4.8.0
2017-12-06 19:06:09 +01:00
Simon Sessingø 56457448e4 Merge pull request #331 from skipperbent/v3
V3
2017-12-02 20:30:37 +01:00
Simon Sessingø 3e7767d978 Merge pull request #330 from skipperbent/v3-development
V3 development
2017-12-02 20:30:27 +01:00
Simon Sessingø 8ce1540771 Merge pull request #328 from skipperbent/v3
V3
2017-11-27 02:05:40 +01:00
Simon Sessingø 5ef9349b18 Merge pull request #327 from skipperbent/v3-development
Version 3.4.6.4
2017-11-27 02:05:17 +01:00
Simon Sessingø 3b305ceb00 Merge pull request #326 from skipperbent/v3
V3
2017-11-27 00:01:45 +01:00
Simon Sessingø 264f0a7b0f Merge pull request #325 from skipperbent/v3-development
Version 3.4.6.3
2017-11-27 00:01:30 +01:00
Simon Sessingø 1f00cf50e6 Merge pull request #324 from skipperbent/v3
V3
2017-11-26 23:44:17 +01:00
Simon Sessingø 5197ae2990 Merge pull request #323 from skipperbent/v3-development
Version 3.4.6.2
2017-11-26 23:44:06 +01:00
Simon Sessingø 32bc46be81 Merge pull request #322 from skipperbent/v3
V3
2017-11-26 18:46:25 +01:00
Simon Sessingø 9c701aabee Merge pull request #321 from skipperbent/v3-development
Fixed url parsing issues
2017-11-26 18:45:54 +01:00
Simon Sessingø 2b0821a557 Merge pull request #320 from skipperbent/v3-development
Fixed typo
2017-11-26 18:29:36 +01:00
Simon Sessingø 1ee71b9ec3 Merge pull request #319 from skipperbent/v3
V3
2017-11-26 18:08:36 +01:00
Simon Sessingø afc81d7005 Merge pull request #318 from skipperbent/v3-development
Version 3.4.6.0
2017-11-26 18:08:24 +01:00
Simon Sessingø 0b8931a2e1 Merge pull request #316 from skipperbent/v3
V3
2017-11-25 01:35:03 +01:00
Simon Sessingø 496d3e7182 Merge pull request #315 from skipperbent/v3-development
Version 3.4.5.5
2017-11-25 01:34:53 +01:00
Simon Sessingø 2472079642 Merge pull request #312 from skipperbent/v3
V3
2017-11-10 13:03:02 +01:00
Simon Sessingø 1fd13ed2aa Merge pull request #311 from skipperbent/v3-development
V3 development
2017-11-10 13:02:49 +01:00
Simon Sessingø 4409fbcf4e Merge pull request #309 from skipperbent/v3
V3
2017-11-10 08:25:22 +01:00
Simon Sessingø 9d5c4a2ed1 Merge pull request #308 from skipperbent/v3-development
3.4.5.4
2017-11-10 08:24:55 +01:00
Simon Sessingø b8634bcf79 Merge pull request #307 from skipperbent/revert-306-revert-305-v3-development
Revert "Revert "V3 development""
2017-11-08 04:11:50 +01:00
Simon Sessingø 6ad22a3816 Revert "Revert "V3 development"" 2017-11-08 04:11:40 +01:00
Simon Sessingø ed41cd55af Merge pull request #306 from skipperbent/revert-305-v3-development
Revert "V3 development"
2017-11-08 04:09:55 +01:00
Simon Sessingø ebeca952cf Revert "V3 development" 2017-11-08 04:09:41 +01:00
Simon Sessingø 0672e85fd7 Merge pull request #305 from skipperbent/v3-development
V3 development
2017-11-08 04:09:31 +01:00
72 changed files with 3836 additions and 1364 deletions
-22
View File
@@ -1,22 +0,0 @@
engines:
phpmd:
enabled: true
checks:
Design/TooManyPublicMethods:
enabled: true
Naming/ShortVariable:
enabled: true
CleanCode/StaticAccess:
enabled: true
Controversial/CamelCaseMethodName:
enabled: true
fixme:
enabled: true
duplication:
enabled: true
config:
languages:
- php:
ratings:
paths:
- src/**
+59
View File
@@ -0,0 +1,59 @@
name: CI
on: [push, pull_request]
jobs:
build-test:
runs-on: ${{ matrix.os }}
env:
PHP_EXTENSIONS: json
PHP_INI_VALUES: assert.exception=1, zend.assertions=1
strategy:
fail-fast: false
matrix:
os:
- ubuntu-latest
- windows-latest
php-version:
- 7.4
- 8.0
phpunit-version:
- 8.5.32
dependencies:
- lowest
- highest
name: PHPUnit Tests
steps:
- name: Configure git to avoid issues with line endings
if: matrix.os == 'windows-latest'
run: git config --global core.autocrlf false
- name: Checkout
uses: actions/checkout@v2
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-version }}
tools: composer:v5, phpunit:${{ matrix.phpunit-versions }}
coverage: xdebug
extensions: ${{ env.PHP_EXTENSIONS }}
ini-values: ${{ env.PHP_INI_VALUES }}
- name: Get composer cache directory
id: composer-cache
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
- name: Cache dependencies
uses: actions/cache@v2
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: php${{ matrix.php-version }}-composer-${{ matrix.dependencies }}-${{ hashFiles('**/composer.json') }}
restore-keys: |
php${{ matrix.php-version }}-composer-${{ matrix.dependencies }}-
- name: Install lowest dependencies with composer
if: matrix.dependencies == 'lowest'
run: composer update --no-ansi --no-interaction --no-progress --prefer-lowest
- name: Install highest dependencies with composer
if: matrix.dependencies == 'highest'
run: composer update --no-ansi --no-interaction --no-progress
- name: Run tests with phpunit
run: composer test
+3 -2
View File
@@ -1,4 +1,5 @@
.idea
composer.lock
vendor/
tests/tmp/*
.idea/
.phpunit.result.cache
tests/tmp
-13
View File
@@ -1,13 +0,0 @@
build:
tests:
override:
-
command: './vendor/bin/phpunit --coverage-clover=coverage.clover'
coverage:
file: 'coverage.clover'
format: 'clover'
checks:
php:
code_rating: true
duplication: true
-13
View File
@@ -1,13 +0,0 @@
sudo: false
language: php
php:
- 7.1
before_script:
- curl -sS http://getcomposer.org/installer | php
- php composer.phar install --prefer-source --no-interaction
script:
- ./vendor/bin/phpunit
+501 -196
View File
File diff suppressed because it is too large Load Diff
+19 -5
View File
@@ -27,16 +27,30 @@
}
],
"require": {
"php": ">=7.1",
"php-di/php-di": "^6.0"
"php": ">=7.4",
"ext-json": "*"
},
"require-dev": {
"phpunit/phpunit": "^6.0",
"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": [
"phpunit tests"
]
},
"autoload": {
"psr-4": {
"Pecee\\": "src/Pecee/"
}
},
"config": {
"allow-plugins": {
"ocramius/package-versions": true
}
}
}
}
+3 -3
View File
@@ -47,14 +47,14 @@ function request(): Request
/**
* Get input class
* @param string|null $index Parameter index name
* @param string|null $defaultValue Default return value
* @param string|mixed|null $defaultValue Default return value
* @param array ...$methods Default methods
* @return \Pecee\Http\Input\InputHandler|\Pecee\Http\Input\IInputItem|string
* @return \Pecee\Http\Input\InputHandler|array|string|null
*/
function input($index = null, $defaultValue = null, ...$methods)
{
if ($index !== null) {
return request()->getInputHandler()->getValue($index, $defaultValue, ...$methods);
return request()->getInputHandler()->value($index, $defaultValue, ...$methods);
}
return request()->getInputHandler();
+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
+3 -3
View File
@@ -9,15 +9,15 @@
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
syntaxCheck="false">
stopOnFailure="false">
<testsuites>
<testsuite name="SimpleRouter Test Suite">
<directory>tests/Pecee/SimpleRouter/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<whitelist addUncoveredFilesFromWhitelist="true"
processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">src</directory>
</whitelist>
</filter>
+14 -14
View File
@@ -6,43 +6,43 @@ interface IResourceController
{
/**
* @return string|null
* @return mixed
*/
public function index(): ?string;
public function index();
/**
* @param mixed $id
* @return string|null
* @return mixed
*/
public function show($id): ?string;
public function show($id);
/**
* @return string|null
* @return mixed
*/
public function store(): ?string;
public function store();
/**
* @return string|null
* @return mixed
*/
public function create(): ?string;
public function create();
/**
* View
* @param mixed $id
* @return string|null
* @return mixed
*/
public function edit($id): ?string;
public function edit($id);
/**
* @param mixed $id
* @return string|null
* @return mixed
*/
public function update($id): ?string;
public function update($id);
/**
* @param mixed $id
* @return string|null
* @return mixed
*/
public function destroy($id): ?string;
public function destroy($id);
}
@@ -2,7 +2,9 @@
namespace Pecee\Http\Exceptions;
class MalformedUrlException extends \Exception
use Exception;
class MalformedUrlException extends Exception
{
}
+10 -4
View File
@@ -9,14 +9,20 @@ interface IInputItem
public function setIndex(string $index): self;
public function getName(): string;
public function getName(): ?string;
public function setName(string $name): self;
public function getValue(): string;
/**
* @return mixed
*/
public function getValue();
public function setValue(string $value): self;
/**
* @param mixed $value
*/
public function setValue($value): self;
public function __toString();
public function __toString(): string;
}
+50 -23
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,9 +74,9 @@ class InputFile implements IInputItem
'error' => null,
];
return (new static($values['index']))
return (new self($values['index']))
->setSize((int)$values['size'])
->setError($values['error'])
->setError((int)$values['error'])
->setType($values['type'])
->setTmpName($values['tmp_name'])
->setFilename($values['name']);
@@ -77,9 +104,9 @@ class InputFile implements IInputItem
}
/**
* @return string
* @return int
*/
public function getSize(): string
public function getSize(): ?int
{
return $this->size;
}
@@ -140,7 +167,7 @@ class InputFile implements IInputItem
*
* @return string
*/
public function getName(): string
public function getName(): ?string
{
return $this->name;
}
@@ -165,7 +192,7 @@ class InputFile implements IInputItem
* @param string $name
* @return static
*/
public function setFilename($name): IInputItem
public function setFilename(string $name): IInputItem
{
$this->filename = $name;
@@ -177,7 +204,7 @@ class InputFile implements IInputItem
*
* @return string mixed
*/
public function getFilename(): string
public function getFilename(): ?string
{
return $this->filename;
}
@@ -188,7 +215,7 @@ class InputFile implements IInputItem
* @param string $destination
* @return bool
*/
public function move($destination): bool
public function move(string $destination): bool
{
return move_uploaded_file($this->tmpName, $destination);
}
@@ -216,9 +243,9 @@ class InputFile implements IInputItem
/**
* Get upload-error code.
*
* @return string
* @return int|null
*/
public function getError(): string
public function getError(): ?int
{
return $this->errors;
}
@@ -226,10 +253,10 @@ class InputFile implements IInputItem
/**
* Set error
*
* @param int $error
* @param int|null $error
* @return static
*/
public function setError($error): IInputItem
public function setError(?int $error): IInputItem
{
$this->errors = (int)$error;
@@ -249,14 +276,14 @@ class InputFile implements IInputItem
* @param string $name
* @return static
*/
public function setTmpName($name): IInputItem
public function setTmpName(string $name): IInputItem
{
$this->tmpName = $name;
return $this;
}
public function __toString()
public function __toString(): string
{
return $this->getTmpName();
}
@@ -267,10 +294,10 @@ class InputFile implements IInputItem
}
/**
* @param string $value
* @param mixed $value
* @return static
*/
public function setValue(string $value): IInputItem
public function setValue($value): IInputItem
{
$this->filename = $value;
+296 -112
View File
@@ -10,22 +10,40 @@ class InputHandler
/**
* @var array
*/
public $get = [];
protected array $get = [];
/**
* @var array
*/
public $post = [];
protected array $post = [];
/**
* @var array
*/
public $file = [];
protected array $file = [];
/**
* @var Request
*/
protected $request;
protected Request $request;
/**
* Original post variables
* @var array
*/
protected array $originalPost = [];
/**
* Original get/params variables
* @var array
*/
protected array $originalParams = [];
/**
* Get original file variables
* @var array
*/
protected array $originalFile = [];
/**
* Input constructor.
@@ -45,39 +63,64 @@ class InputHandler
public function parseInputs(): void
{
/* Parse get requests */
if (\count($_GET) !== 0) {
$this->get = $this->handleGetPost($_GET);
if (count($_GET) !== 0) {
$this->originalParams = $_GET;
$this->get = $this->parseInputItem($this->originalParams);
}
/* Parse post requests */
$postVars = $_POST;
$this->originalPost = $_POST;
if (\in_array($this->request->getMethod(), ['put', 'patch', 'delete'], false) === true) {
parse_str(file_get_contents('php://input'), $postVars);
if ($this->request->isPostBack() === true) {
$contents = file_get_contents('php://input');
// Append any PHP-input json
if (strpos(trim($contents), '{') === 0) {
$post = json_decode($contents, true);
if ($post !== false) {
$this->originalPost += $post;
}
} else {
$post = [];
parse_str($contents, $post);
$this->originalPost += $post;
}
}
if (\count($postVars) !== 0) {
$this->post = $this->handleGetPost($postVars);
if (count($this->originalPost) !== 0) {
$this->post = $this->parseInputItem($this->originalPost);
}
/* Parse get requests */
if (\count($_FILES) !== 0) {
$this->file = $this->parseFiles();
if (count($_FILES) !== 0) {
$this->originalFile = $_FILES;
$this->file = $this->parseFiles($this->originalFile);
}
}
/**
* @param array $files Array with files to parse
* @param string|null $parentKey Key from parent (used when parsing nested array).
* @return array
*/
public function parseFiles(): array
public function parseFiles(array $files, ?string $parentKey = null): array
{
$list = [];
foreach ((array)$_FILES as $key => $value) {
foreach ($files as $key => $value) {
// Parse multi dept file array
if (isset($value['name']) === false && is_array($value) === true) {
$list[$key] = $this->parseFiles($value, $key);
continue;
}
// Handle array input
if (\is_array($value['name']) === false) {
$values['index'] = $key;
if (is_array($value['name']) === false) {
$values = ['index' => $parentKey ?? $key];
try {
$list[$key] = InputFile::createFromArray($values + $value);
} catch (InvalidArgumentException $e) {
@@ -87,7 +130,7 @@ class InputHandler
}
$keys = [$key];
$files = $this->rearrangeFiles($value['name'], $keys, $value);
$files = $this->rearrangeFile($value['name'], $keys, $value);
if (isset($list[$key]) === true) {
$list[$key][] = $files;
@@ -100,9 +143,16 @@ class InputHandler
return $list;
}
protected function rearrangeFiles(array $values, &$index, $original): array
/**
* Rearrange multi-dimensional file object created by PHP.
*
* @param array $values
* @param array $index
* @param array|null $original
* @return array
*/
protected function rearrangeFile(array $values, array &$index, ?array $original): array
{
$originalIndex = $index[0];
array_shift($index);
@@ -110,17 +160,17 @@ class InputHandler
foreach ($values as $key => $value) {
if (\is_array($original['name'][$key]) === false) {
if (is_array($original['name'][$key]) === false) {
try {
$file = InputFile::createFromArray([
'index' => (empty($key) === true && empty($originalIndex) === false) ? $originalIndex : $key,
'name' => $original['name'][$key],
'error' => $original['error'][$key],
'index' => ($key === '' && $originalIndex !== '') ? $originalIndex : $key,
'name' => $original['name'][$key],
'error' => $original['error'][$key],
'tmp_name' => $original['tmp_name'][$key],
'type' => $original['type'][$key],
'size' => $original['size'][$key],
'type' => $original['type'][$key],
'size' => $original['size'][$key],
]);
if (isset($output[$key]) === true) {
@@ -138,7 +188,7 @@ class InputHandler
$index[] = $key;
$files = $this->rearrangeFiles($value, $index, $original);
$files = $this->rearrangeFile($value, $index, $original);
if (isset($output[$key]) === true) {
$output[$key][] = $files;
@@ -151,34 +201,133 @@ class InputHandler
return $output;
}
protected function handleGetPost(array $array): array
/**
* Parse input item from array
*
* @param array $array
* @return array
*/
protected function parseInputItem(array $array): array
{
$list = [];
foreach ($array as $key => $value) {
// Handle array input
if (\is_array($value) === false) {
$list[$key] = new InputItem($key, $value);
continue;
if (is_array($value) === true) {
$value = $this->parseInputItem($value);
}
$output = $this->handleGetPost($value);
$list[$key] = $output;
$list[$key] = new InputItem($key, $value);
}
return $list;
}
/**
* Find input object
*
* @param string $index
* @param array ...$methods
* @return IInputItem|array|null
*/
public function find(string $index, ...$methods)
{
$element = null;
if (count($methods) > 0) {
$methods = is_array(...$methods) ? array_values(...$methods) : $methods;
}
if (count($methods) === 0 || in_array(Request::REQUEST_TYPE_GET, $methods, true) === true) {
$element = $this->get($index);
}
if (($element === null && count($methods) === 0) || (count($methods) !== 0 && in_array(Request::REQUEST_TYPE_POST, $methods, true) === true)) {
$element = $this->post($index);
}
if (($element === null && count($methods) === 0) || (count($methods) !== 0 && in_array('file', $methods, true) === true)) {
$element = $this->file($index);
}
return $element;
}
protected function getValueFromArray(array $array): array
{
$output = [];
/* @var $item InputItem */
foreach ($array as $key => $item) {
if ($item instanceof IInputItem) {
$item = $item->getValue();
}
$output[$key] = is_array($item) ? $this->getValueFromArray($item) : $item;
}
return $output;
}
/**
* Get input element value matching index
*
* @param string $index
* @param string|mixed|null $defaultValue
* @param array ...$methods
* @return string|array
*/
public function value(string $index, $defaultValue = null, ...$methods)
{
$input = $this->find($index, ...$methods);
if ($input instanceof IInputItem) {
$input = $input->getValue();
}
/* Handle collection */
if (is_array($input) === true) {
$output = $this->getValueFromArray($input);
return (count($output) === 0) ? $defaultValue : $output;
}
return ($input === null || (is_string($input) && trim($input) === '')) ? $defaultValue : $input;
}
/**
* Check if a input-item exist.
* If an array is as $index parameter the method returns true if all elements exist.
*
* @param string|array $index
* @param array ...$methods
* @return 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;
}
/**
* Find post-value by index or return default value.
*
* @param string $index
* @param string|null $defaultValue
* @return InputItem|string
* @param mixed|null $defaultValue
* @return InputItem|array|string|null
*/
public function findPost(string $index, ?string $defaultValue = null)
public function post(string $index, $defaultValue = null)
{
return $this->post[$index] ?? $defaultValue;
}
@@ -187,10 +336,10 @@ class InputHandler
* Find file by index or return default value.
*
* @param string $index
* @param string|null $defaultValue
* @return InputFile|string
* @param mixed|null $defaultValue
* @return InputFile|array|string|null
*/
public function findFile(string $index, ?string $defaultValue = null)
public function file(string $index, $defaultValue = null)
{
return $this->file[$index] ?? $defaultValue;
}
@@ -199,92 +348,127 @@ class InputHandler
* Find parameter/query-string by index or return default value.
*
* @param string $index
* @param string|null $defaultValue
* @return InputItem|string
* @param mixed|null $defaultValue
* @return InputItem|array|string|null
*/
public function findGet(string $index, ?string $defaultValue = null)
public function get(string $index, $defaultValue = null)
{
return $this->get[$index] ?? $defaultValue;
}
/**
* Get input object
*
* @param string $index
* @param array ...$methods
* @return IInputItem|null
*/
public function get(string $index, ...$methods): ?IInputItem
{
$element = null;
if (\count($methods) === 0 || \in_array('get', $methods, true) === true) {
$element = $this->findGet($index);
}
if (($element === null && \count($methods) === 0) || (\count($methods) === 0 && \in_array('post', $methods, true) === true)) {
$element = $this->findPost($index);
}
if (($element === null && \count($methods) === 0) || (\count($methods) === 0 && \in_array('file', $methods, true) === true)) {
$element = $this->findFile($index);
}
return $element;
}
/**
* Get input element value matching index
*
* @param string $index
* @param string|null $defaultValue
* @param array ...$methods
* @return string
*/
public function getValue(string $index, ?string $defaultValue = null, ...$methods): ?string
{
$input = $this->get($index, ...$methods);
return ($input === null || ($input !== null && trim($input->getValue()) === '')) ? $defaultValue : $input->getValue();
}
/**
* Check if a input-item exist
*
* @param string $index
* @param array ...$method
* @return bool
*/
public function exists(string $index, ...$method): bool
{
return $this->get($index, $method) !== null;
}
/**
* Get all get/post items
* @param array|null $filter Only take items in filter
* @param array $filter Only take items in filter
* @return array
*/
public function all(array $filter = null): array
public function all(array $filter = []): array
{
$output = $_GET;
$output = $this->originalParams + $this->originalPost + $this->originalFile;
$output = (count($filter) > 0) ? array_intersect_key($output, array_flip($filter)) : $output;
if ($this->request->getMethod() === 'post') {
// Append POST data
$output += $_POST;
$contents = file_get_contents('php://input');
// Append any PHP-input json
if (strpos(trim($contents), '{') === 0) {
$post = json_decode($contents, true);
if ($post !== false) {
$output += $post;
}
foreach ($filter as $filterKey) {
if (array_key_exists($filterKey, $output) === false) {
$output[$filterKey] = null;
}
}
return ($filter !== null) ? array_intersect_key($output, array_flip($filter)) : $output;
return $output;
}
/**
* Add GET parameter
*
* @param string $key
* @param InputItem $item
*/
public function addGet(string $key, InputItem $item): void
{
$this->get[$key] = $item;
}
/**
* Add POST parameter
*
* @param string $key
* @param InputItem $item
*/
public function addPost(string $key, InputItem $item): void
{
$this->post[$key] = $item;
}
/**
* Add FILE parameter
*
* @param string $key
* @param InputFile $item
*/
public function addFile(string $key, InputFile $item): void
{
$this->file[$key] = $item;
}
/**
* Get original post variables
* @return array
*/
public function getOriginalPost(): array
{
return $this->originalPost;
}
/**
* Set original post variables
* @param array $post
* @return static $this
*/
public function setOriginalPost(array $post): self
{
$this->originalPost = $post;
return $this;
}
/**
* Get original get variables
* @return array
*/
public function getOriginalParams(): array
{
return $this->originalParams;
}
/**
* Set original get-variables
* @param array $params
* @return static $this
*/
public function setOriginalParams(array $params): self
{
$this->originalParams = $params;
return $this;
}
/**
* Get original file variables
* @return array
*/
public function getOriginalFile(): array
{
return $this->originalFile;
}
/**
* Set original file posts variables
* @param array $file
* @return static $this
*/
public function setOriginalFile(array $file): self
{
$this->originalFile = $file;
return $this;
}
}
+54 -11
View File
@@ -2,13 +2,25 @@
namespace Pecee\Http\Input;
class InputItem implements IInputItem
use ArrayAccess;
use ArrayIterator;
use IteratorAggregate;
class InputItem implements ArrayAccess, IInputItem, IteratorAggregate
{
public $index;
public $name;
public string $index;
public string $name;
/**
* @var mixed|null
*/
public $value;
public function __construct(string $index, ?string $value = null)
/**
* @param string $index
* @param mixed $value
*/
public function __construct(string $index, $value = null)
{
$this->index = $index;
$this->value = $value;
@@ -35,7 +47,7 @@ class InputItem implements IInputItem
/**
* @return string
*/
public function getName(): string
public function getName(): ?string
{
return $this->name;
}
@@ -53,28 +65,59 @@ class InputItem implements IInputItem
}
/**
* @return string
* @return mixed
*/
public function getValue(): string
public function getValue()
{
return $this->value;
}
/**
* Set input value
* @param string $value
* @param mixed $value
* @return static
*/
public function setValue(string $value): IInputItem
public function setValue($value): IInputItem
{
$this->value = $value;
return $this;
}
public function __toString()
public function offsetExists($offset): bool
{
return (string)$this->value;
return isset($this->value[$offset]);
}
#[\ReturnTypeWillChange]
public function offsetGet($offset): ?self
{
if ($this->offsetExists($offset) === true) {
return $this->value[$offset];
}
return null;
}
public function offsetSet($offset, $value): void
{
$this->value[$offset] = $value;
}
public function offsetUnset($offset): void
{
unset($this->value[$offset]);
}
public function __toString(): string
{
$value = $this->getValue();
return (is_array($value) === true) ? json_encode($value) : $value;
}
public function getIterator(): ArrayIterator
{
return new ArrayIterator($this->getValue());
}
}
+48 -17
View File
@@ -9,21 +9,52 @@ use Pecee\Http\Security\ITokenProvider;
class BaseCsrfVerifier implements IMiddleware
{
public const POST_KEY = 'csrf-token';
public const POST_KEY = 'csrf_token';
public const HEADER_KEY = 'X-CSRF-TOKEN';
protected $except;
protected $tokenProvider;
/**
* Urls to ignore. You can use * to exclude all sub-urls on a given path.
* For example: /admin/*
* @var array|null
*/
protected array $except = [];
/**
* Urls to include. Can be used to include urls from a certain path.
* @var array|null
*/
protected array $include = [];
/**
* @var ITokenProvider
*/
protected ITokenProvider $tokenProvider;
/**
* BaseCsrfVerifier constructor.
* @throws \Pecee\Http\Security\Exceptions\SecurityException
*/
public function __construct()
{
$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
@@ -31,24 +62,27 @@ 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;
}
$max = \count($this->except) - 1;
for ($i = $max; $i >= 0; $i--) {
$url = $this->except[$i];
foreach ($this->except as $url) {
$url = rtrim($url, '/');
if ($url[\strlen($url) - 1] === '*') {
if ($url[strlen($url) - 1] === '*') {
$url = rtrim($url, '*');
$skip = $request->getUrl()->contains($url);
} else {
$skip = ($url === $request->getUrl()->getOriginalUrl());
$skip = ($url === rtrim($request->getUrl()->getRelativeUrl(false), '/'));
}
if ($skip === true) {
$skip = !$this->isIncluded($request);
if ($skip === false) {
continue;
}
return true;
}
}
@@ -64,13 +98,11 @@ class BaseCsrfVerifier implements IMiddleware
*/
public function handle(Request $request): void
{
if ($this->skip($request) === false && ($request->isPostBack() === true || $request->isPostBack() === true && $this->isIncluded($request) === true)) {
if ($this->skip($request) === false && \in_array($request->getMethod(), ['post', 'put', 'delete'], true) === true) {
$token = $request->getInputHandler()->getValue(
$token = $request->getInputHandler()->value(
static::POST_KEY,
$request->getHeader(static::HEADER_KEY),
'post'
);
if ($this->tokenProvider->validate((string)$token) === false) {
@@ -81,7 +113,6 @@ class BaseCsrfVerifier implements IMiddleware
// Refresh existing token
$this->tokenProvider->refresh();
}
public function getTokenProvider(): ITokenProvider
@@ -2,7 +2,9 @@
namespace Pecee\Http\Middleware\Exceptions;
class TokenMismatchException extends \Exception
use Exception;
class TokenMismatchException extends Exception
{
}
@@ -0,0 +1,47 @@
<?php
namespace Pecee\Http\Middleware;
use Pecee\Http\Request;
use Pecee\SimpleRouter\Exceptions\HttpException;
abstract class IpRestrictAccess implements IMiddleware
{
protected array $ipBlacklist = [];
protected array $ipWhitelist = [];
protected function validate(string $ip): bool
{
// Accept ip that is in white-list
if(in_array($ip, $this->ipWhitelist, true) === true) {
return true;
}
foreach ($this->ipBlacklist as $blackIp) {
// Blocks range (8.8.*)
if ($blackIp[strlen($blackIp) - 1] === '*' && strpos($ip, trim($blackIp, '*')) === 0) {
return false;
}
// Blocks exact match
if ($blackIp === $ip) {
return false;
}
}
return true;
}
/**
* @param Request $request
* @throws HttpException
*/
public function handle(Request $request): void
{
if($this->validate((string)$request->getIp()) === false) {
throw new HttpException(sprintf('Restricted ip. Access to %s has been blocked', $request->getIp()), 403);
}
}
}
+177 -37
View File
@@ -4,70 +4,116 @@ namespace Pecee\Http;
use Pecee\Http\Exceptions\MalformedUrlException;
use Pecee\Http\Input\InputHandler;
use Pecee\Http\Middleware\BaseCsrfVerifier;
use Pecee\SimpleRouter\Route\ILoadableRoute;
use Pecee\SimpleRouter\Route\RouteUrl;
use Pecee\SimpleRouter\SimpleRouter;
class Request
{
public const REQUEST_TYPE_GET = 'get';
public const REQUEST_TYPE_POST = 'post';
public const REQUEST_TYPE_PUT = 'put';
public const REQUEST_TYPE_PATCH = 'patch';
public const REQUEST_TYPE_OPTIONS = 'options';
public const REQUEST_TYPE_DELETE = 'delete';
public const REQUEST_TYPE_HEAD = 'head';
public const CONTENT_TYPE_JSON = 'application/json';
public const CONTENT_TYPE_FORM_DATA = 'multipart/form-data';
public const CONTENT_TYPE_X_FORM_ENCODED = 'application/x-www-form-urlencoded';
public const FORCE_METHOD_KEY = '_method';
/**
* All request-types
* @var string[]
*/
public static array $requestTypes = [
self::REQUEST_TYPE_GET,
self::REQUEST_TYPE_POST,
self::REQUEST_TYPE_PUT,
self::REQUEST_TYPE_PATCH,
self::REQUEST_TYPE_OPTIONS,
self::REQUEST_TYPE_DELETE,
self::REQUEST_TYPE_HEAD,
];
/**
* Post request-types.
* @var string[]
*/
public static array $requestTypesPost = [
self::REQUEST_TYPE_POST,
self::REQUEST_TYPE_PUT,
self::REQUEST_TYPE_PATCH,
self::REQUEST_TYPE_DELETE,
];
/**
* Additional data
*
* @var array
*/
private $data = [];
private array $data = [];
/**
* Server headers
* @var array
*/
protected $headers = [];
protected array $headers = [];
/**
* Request ContentType
* @var string
*/
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.
@@ -77,21 +123,26 @@ class Request
{
foreach ($_SERVER as $key => $value) {
$this->headers[strtolower($key)] = $value;
$this->headers[strtolower(str_replace('_', '-', $key))] = $value;
$this->headers[str_replace('_', '-', strtolower($key))] = $value;
}
$this->setHost($this->getHeader('http-host'));
// Check if special IIS header exist, otherwise use default.
$this->setUrl(new Url($this->getHeader('unencoded-url', $this->getHeader('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);
$this->method = strtolower($this->inputHandler->getValue('_method', $this->getHeader('request-method')));
}
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;
}
/**
@@ -146,6 +197,15 @@ class Request
return $this->getHeader('php-auth-pw');
}
/**
* Get the csrf token
* @return string|null
*/
public function getCsrfToken(): ?string
{
return $this->getHeader(BaseCsrfVerifier::HEADER_KEY);
}
/**
* Get all headers
* @return array
@@ -157,19 +217,25 @@ class Request
/**
* Get id address
* If $safe is false, this function will detect Proxys. But the user can edit this header to whatever he wants!
* https://stackoverflow.com/questions/3003145/how-to-get-the-client-ip-address-in-php#comment-25086804
* @param bool $safeMode When enabled, only safe non-spoofable headers will be returned. Note this can cause issues when using proxy.
* @return string|null
*/
public function getIp(): ?string
public function getIp(bool $safeMode = false): ?string
{
if ($this->getHeader('http-cf-connecting-ip') !== null) {
return $this->getHeader('http-cf-connecting-ip');
$headers = [];
if ($safeMode === false) {
$headers = [
'http-cf-connecting-ip',
'http-client-ip',
'http-x-forwarded-for',
];
}
if ($this->getHeader('http-x-forwarded-for') !== null) {
return $this->getHeader('http-x-forwarded_for');
}
$headers[] = 'remote-addr';
return $this->getHeader('remote-addr');
return $this->getFirstHeader($headers);
}
/**
@@ -204,14 +270,72 @@ class Request
/**
* Get header value by name
*
* @param string $name
* @param string|null $defaultValue
* @param string $name Name of the header.
* @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($name, $defaultValue = null): ?string
public function getHeader(string $name, $defaultValue = null, bool $tryParse = true): ?string
{
return $this->headers[strtolower($name)] ?? $defaultValue;
$name = strtolower($name);
$header = $this->headers[$name] ?? null;
if ($tryParse === true && $header === null) {
if (strpos($name, 'http-') === 0) {
// Trying to find client header variant which was not found, searching for header variant without http- prefix.
$header = $this->headers[str_replace('http-', '', $name)] ?? null;
} else {
// Trying to find server variant which was not found, searching for client variant with http- prefix.
$header = $this->headers['http-' . $name] ?? null;
}
}
return $header ?? $defaultValue;
}
/**
* Will try to find first header from list of headers.
*
* @param array $headers
* @param mixed|null $defaultValue
* @return mixed|null
*/
public function getFirstHeader(array $headers, $defaultValue = null)
{
foreach ($headers as $header) {
$header = $this->getHeader($header);
if ($header !== null) {
return $header;
}
}
return $defaultValue;
}
/**
* Get request content-type
* @return string|null
*/
public function getContentType(): ?string
{
return $this->contentType;
}
/**
* Set request content-type
* @param string $contentType
* @return $this
*/
protected function setContentType(string $contentType): self
{
if (strpos($contentType, ';') > 0) {
$this->contentType = strtolower(substr($contentType, 0, strpos($contentType, ';')));
} else {
$this->contentType = strtolower($contentType);
}
return $this;
}
/**
@@ -230,7 +354,7 @@ class Request
*
* @return bool
*/
public function isFormatAccepted($format): bool
public function isFormatAccepted(string $format): bool
{
return ($this->getHeader('http-accept') !== null && stripos($this->getHeader('http-accept'), $format) !== false);
}
@@ -242,7 +366,17 @@ class Request
*/
public function isAjax(): bool
{
return (strtolower($this->getHeader('http-x-requested-with')) === 'xmlhttprequest');
return (strtolower((string)$this->getHeader('http-x-requested-with')) === 'xmlhttprequest');
}
/**
* Returns true when request-method is type that could contain data in the page body.
*
* @return bool
*/
public function isPostBack(): bool
{
return in_array($this->getMethod(), static::$requestTypesPost, true);
}
/**
@@ -261,8 +395,12 @@ class Request
{
$this->url = $url;
if ($this->url->getHost() === null) {
$this->url->setHost((string)$this->getHost());
if ($this->getHost() !== null) {
$url->setHost($this->getHost());
}
if ($this->isSecure() === true) {
$this->url->setScheme('https');
}
}
@@ -271,6 +409,11 @@ class Request
*/
public function setHost(?string $host): void
{
// Strip any potential ports from hostname
if (strpos((string)$host, ':') !== false) {
$host = strstr($host, strrchr($host, ':'), true);
}
$this->host = $host;
}
@@ -279,7 +422,7 @@ class Request
*/
public function setMethod(string $method): void
{
$this->method = $method;
$this->method = strtolower($method);
}
/**
@@ -348,7 +491,7 @@ class Request
*/
public function getLoadedRoute(): ?ILoadableRoute
{
return (\count($this->loadedRoutes) > 0) ? end($this->loadedRoutes) : null;
return (count($this->loadedRoutes) > 0) ? end($this->loadedRoutes) : null;
}
/**
@@ -370,7 +513,6 @@ class Request
public function setLoadedRoutes(array $routes): self
{
$this->loadedRoutes = $routes;
return $this;
}
@@ -383,7 +525,6 @@ class Request
public function addLoadedRoute(ILoadableRoute $route): self
{
$this->loadedRoutes[] = $route;
return $this;
}
@@ -406,11 +547,10 @@ class Request
public function setHasPendingRewrite(bool $boolean): self
{
$this->hasPendingRewrite = $boolean;
return $this;
}
public function __isset($name)
public function __isset($name): bool
{
return array_key_exists($name, $this->data) === true;
}
+9 -7
View File
@@ -2,11 +2,12 @@
namespace Pecee\Http;
use JsonSerializable;
use Pecee\Exceptions\InvalidArgumentException;
class Response
{
protected $request;
protected Request $request;
public function __construct(Request $request)
{
@@ -30,7 +31,9 @@ class Response
* Redirect the response
*
* @param string $url
* @param int $httpCode
* @param ?int $httpCode
*
* @return never
*/
public function redirect(string $url, ?int $httpCode = null): void
{
@@ -64,7 +67,6 @@ class Response
public function cache(string $eTag, int $lastModifiedTime = 2592000): self
{
$this->headers([
'Cache-Control: public',
sprintf('Last-Modified: %s GMT', gmdate('D, d M Y H:i:s', $lastModifiedTime)),
@@ -85,14 +87,14 @@ class Response
/**
* 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 $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) {
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.');
}
@@ -127,4 +129,4 @@ class Response
return $this;
}
}
}
@@ -2,14 +2,22 @@
namespace Pecee\Http\Security;
use Exception;
use Pecee\Http\Security\Exceptions\SecurityException;
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.
@@ -17,7 +25,7 @@ class CookieTokenProvider implements ITokenProvider
*/
public function __construct()
{
$this->token = $this->getToken();
$this->token = ($this->hasToken() === true) ? $_COOKIE[static::CSRF_KEY] : null;
if ($this->token === null) {
$this->token = $this->generateToken();
@@ -34,7 +42,7 @@ class CookieTokenProvider implements ITokenProvider
{
try {
return bin2hex(random_bytes(32));
} catch (\Exception $e) {
} catch (Exception $e) {
throw new SecurityException($e->getMessage(), (int)$e->getCode(), $e->getPrevious());
}
}
@@ -63,7 +71,7 @@ class CookieTokenProvider implements ITokenProvider
public function setToken(string $token): void
{
$this->token = $token;
setcookie(static::CSRF_KEY, $token, (int)((time() + 60) * $this->cookieTimeoutMinutes), '/', ini_get('session.cookie_domain'), ini_get('session.cookie_secure'), ini_get('session.cookie_httponly'));
setcookie(static::CSRF_KEY, $token, time() + (60 * $this->cookieTimeoutMinutes), '/', ini_get('session.cookie_domain'), ini_get('session.cookie_secure'), ini_get('session.cookie_httponly'));
}
/**
@@ -73,8 +81,6 @@ class CookieTokenProvider implements ITokenProvider
*/
public function getToken(?string $defaultValue = null): ?string
{
$this->token = ($this->hasToken() === true) ? $_COOKIE[static::CSRF_KEY] : null;
return $this->token ?? $defaultValue;
}
@@ -2,7 +2,9 @@
namespace Pecee\Http\Security\Exceptions;
class SecurityException extends \Exception
use Exception;
class SecurityException extends Exception
{
}
+150 -52
View File
@@ -2,52 +2,101 @@
namespace Pecee\Http;
use JsonSerializable;
use Pecee\Http\Exceptions\MalformedUrlException;
class Url
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.
*
* @param string $url
* @param ?string $url
* @throws MalformedUrlException
*/
public function __construct(?string $url)
{
$this->originalUrl = $url;
$this->parse($url, true);
}
if ($url !== null && $url !== '/') {
public function parse(?string $url, bool $setOriginalPath = false): self
{
if ($url !== null) {
$data = $this->parseUrl($url);
$this->scheme = $data['scheme'] ?? null;
$this->host = $data['host'] ?? null;
$this->scheme = $data['scheme'] ?? $this->scheme;
$this->host = $data['host'] ?? $this->host;
$this->port = $data['port'] ?? null;
$this->username = $data['user'] ?? null;
$this->password = $data['pass'] ?? null;
if (isset($data['path']) === true) {
$this->setPath($data['path']);
if ($setOriginalPath === true) {
$this->originalPath = $data['path'];
}
}
$this->fragment = $data['fragment'] ?? null;
if (isset($data['query']) === true) {
$params = [];
parse_str($data['query'], $params);
$this->setParams($params);
$this->setQueryString($data['query']);
}
}
return $this;
}
/**
@@ -96,10 +145,15 @@ class Url
/**
* Get url host
*
* @param bool $includeTrails Prepend // in front of hostname
* @return string|null
*/
public function getHost(): ?string
public function getHost(bool $includeTrails = false): ?string
{
if ((string)$this->host !== '' && $includeTrails === true) {
return '//' . $this->host;
}
return $this->host;
}
@@ -193,6 +247,15 @@ class Url
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
*
@@ -240,6 +303,24 @@ class Url
return $this;
}
/**
* Set raw query-string parameters as string
*
* @param string $queryString
* @return static
*/
public function setQueryString(string $queryString): self
{
$params = [];
parse_str($queryString, $params);
if (count($params) > 0) {
return $this->setParams($params);
}
return $this;
}
/**
* Get query-string params as string
*
@@ -314,25 +395,36 @@ class Url
*/
public function hasParam(string $name): bool
{
return \in_array($name, $this->getParams(), true);
return array_key_exists($name, $this->getParams());
}
/**
* Removes parameter from query-string
* Removes multiple parameters from the query-string
*
* @param array ...$names
* @return static
*/
public function removeParams(...$names): self
{
$params = array_diff_key($this->getParams(), array_flip(...$names));
$this->setParams($params);
return $this;
}
/**
* Removes parameter from the query-string
*
* @param string $name
* @return static
*/
public function removeParam(string $name): void
public function removeParam(string $name): self
{
if ($this->hasParam($name) === true) {
$params = $this->getParams();
$key = \array_search($name, $params, true);
$params = $this->getParams();
unset($params[$name]);
$this->setParams($params);
if ($key === true) {
unset($params[$key]);
$this->setParams($params);
}
}
return $this;
}
/**
@@ -345,18 +437,7 @@ class Url
*/
public function getParam(string $name, ?string $defaultValue = null): ?string
{
$output = null;
if ($this->hasParam($name) === true) {
$params = $this->getParams();
$key = \array_search($name, $params, true);
if ($key === true) {
$output = $params[$key];
}
}
return $output ?? $defaultValue;
return (isset($this->getParams()[$name]) === true) ? $this->getParams()[$name] : $defaultValue;
}
/**
@@ -370,7 +451,7 @@ class Url
{
$encodedUrl = preg_replace_callback(
'/[^:\/@?&=#]+/u',
function ($matches) {
static function ($matches): string {
return urlencode($matches[0]);
},
$url
@@ -394,10 +475,10 @@ class Url
*/
public static function arrayToParams(array $getParams = [], bool $includeEmpty = true): string
{
if (\count($getParams) !== 0) {
if (count($getParams) !== 0) {
if ($includeEmpty === false) {
$getParams = array_filter($getParams, function ($item) {
$getParams = array_filter($getParams, static function ($item): bool {
return (trim($item) !== '');
});
}
@@ -411,14 +492,18 @@ class Url
/**
* Returns the relative url
*
* @param bool $includeParams
* @return string
*/
public function getRelativeUrl(): string
public function getRelativeUrl(bool $includeParams = true): string
{
$params = $this->getQueryString();
$path = $this->path ?? '/';
$path = $this->path ?? '';
$query = $params !== '' ? '?' . $params : '';
if ($includeParams === false) {
return $path;
}
$query = $this->getQueryString() !== '' ? '?' . $this->getQueryString() : '';
$fragment = $this->fragment !== null ? '#' . $this->fragment : '';
return $path . $query . $fragment;
@@ -427,21 +512,34 @@ class Url
/**
* Returns the absolute url
*
* @param bool $includeParams
* @return string
*/
public function getAbsoluteUrl(): 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();
return $scheme . $user . $pass . $host . $port . $this->getRelativeUrl($includeParams);
}
public function __toString()
/**
* Specify data which should be serialized to JSON
* @link http://php.net/manual/en/jsonserializable.jsonserialize.php
* @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
*/
public function jsonSerialize(): string
{
return $this->getHost(true) . $this->getRelativeUrl();
}
public function __toString(): string
{
return $this->getRelativeUrl();
}
@@ -2,117 +2,48 @@
namespace Pecee\SimpleRouter\ClassLoader;
use DI\Container;
use Pecee\SimpleRouter\Exceptions\NotFoundHttpException;
use Pecee\SimpleRouter\Exceptions\ClassNotFoundHttpException;
class ClassLoader implements IClassLoader
{
/**
* Dependency injection enabled
* @var bool
*/
protected $useDependencyInjection = false;
/**
* @var Container|null
*/
protected $container;
/**
* Load class
*
* @param string $class
* @return mixed
* @throws NotFoundHttpException
* @return object
* @throws ClassNotFoundHttpException
*/
public function loadClass(string $class)
{
if (class_exists($class) === false) {
throw new NotFoundHttpException(sprintf('Class "%s" does not exist', $class), 404);
}
if ($this->useDependencyInjection === true) {
$container = $this->getContainer();
if ($container !== null) {
try {
return $container->get($class);
} catch (\Exception $e) {
throw new NotFoundHttpException($e->getMessage(), (int)$e->getCode(), $e->getPrevious());
}
}
throw new ClassNotFoundHttpException($class, null, sprintf('Class "%s" does not exist', $class), 404, null);
}
return new $class();
}
/**
* Called when loading class method
* @param object $class
* @param string $method
* @param array $parameters
* @return string
*/
public function loadClassMethod($class, string $method, array $parameters): string
{
return (string)call_user_func_array([$class, $method], array_values($parameters));
}
/**
* Load closure
*
* @param \Closure $closure
* @param Callable $closure
* @param array $parameters
* @return mixed
* @throws NotFoundHttpException
* @return string
*/
public function loadClosure(\Closure $closure, array $parameters)
public function loadClosure(callable $closure, array $parameters): string
{
if ($this->useDependencyInjection === true) {
$container = $this->getContainer();
if ($container !== null) {
try {
return $container->call($closure, $parameters);
} catch (\Exception $e) {
throw new NotFoundHttpException($e->getMessage(), (int)$e->getCode(), $e->getPrevious());
}
}
}
return \call_user_func_array($closure, $parameters);
}
/**
* Get dependency injector container.
*
* @return Container|null
*/
public function getContainer(): ?Container
{
return $this->container;
}
/**
* Set the dependency-injector container.
*
* @param Container $container
* @return ClassLoader
*/
public function setContainer(Container $container): self
{
$this->container = $container;
return $this;
}
/**
* Enable or disable dependency injection.
*
* @param bool $enabled
* @return static
*/
public function useDependencyInjection(bool $enabled): self
{
$this->useDependencyInjection = $enabled;
return $this;
}
/**
* Return true if dependency injection is enabled.
*
* @return bool
*/
public function isDependencyInjectionEnabled(): bool
{
return $this->useDependencyInjection;
return (string)call_user_func_array($closure, array_values($parameters));
}
}
@@ -5,8 +5,29 @@ namespace Pecee\SimpleRouter\ClassLoader;
interface IClassLoader
{
/**
* Called when loading class
* @param string $class
* @return object
*/
public function loadClass(string $class);
public function loadClosure(\Closure $closure, array $parameters);
/**
* Called when loading class method
* @param object $class
* @param string $method
* @param array $parameters
* @return mixed
*/
public function loadClassMethod($class, string $method, array $parameters);
/**
* Called when loading method
*
* @param callable $closure
* @param array $parameters
* @return mixed
*/
public function loadClosure(Callable $closure, array $parameters);
}
+10 -9
View File
@@ -2,6 +2,7 @@
namespace Pecee\SimpleRouter\Event;
use InvalidArgumentException;
use Pecee\Http\Request;
use Pecee\SimpleRouter\Router;
@@ -11,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;
@@ -74,7 +75,7 @@ class EventArgument implements IEventArgument
* @param string $name
* @return mixed
*/
public function __get($name)
public function __get(string $name)
{
return $this->arguments[$name] ?? null;
}
@@ -83,7 +84,7 @@ class EventArgument implements IEventArgument
* @param string $name
* @return bool
*/
public function __isset($name)
public function __isset(string $name): bool
{
return array_key_exists($name, $this->arguments);
}
@@ -91,11 +92,11 @@ class EventArgument implements IEventArgument
/**
* @param string $name
* @param mixed $value
* @throws \InvalidArgumentException
* @throws InvalidArgumentException
*/
public function __set($name, $value)
public function __set(string $name, $value): void
{
throw new \InvalidArgumentException('Not supported');
throw new InvalidArgumentException('Not supported');
}
/**
@@ -0,0 +1,45 @@
<?php
namespace Pecee\SimpleRouter\Exceptions;
use Throwable;
class ClassNotFoundHttpException extends NotFoundHttpException
{
/**
* @var string
*/
protected string $class;
/**
* @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);
$this->class = $class;
$this->method = $method;
}
/**
* Get class name
* @return string
*/
public function getClass(): string
{
return $this->class;
}
/**
* Get method
* @return string|null
*/
public function getMethod(): ?string
{
return $this->method;
}
}
@@ -2,7 +2,9 @@
namespace Pecee\SimpleRouter\Exceptions;
class HttpException extends \Exception
use Exception;
class HttpException extends Exception
{
}
@@ -2,6 +2,8 @@
namespace Pecee\SimpleRouter\Handlers;
use Closure;
use Exception;
use Pecee\Http\Request;
/**
@@ -15,21 +17,24 @@ use Pecee\Http\Request;
class CallbackExceptionHandler implements IExceptionHandler
{
protected $callback;
/**
* @var Closure
*/
protected Closure $callback;
public function __construct(\Closure $callback)
public function __construct(Closure $callback)
{
$this->callback = $callback;
}
/**
* @param Request $request
* @param \Exception $error
* @param Exception $error
*/
public function handleError(Request $request, \Exception $error): void
public function handleError(Request $request, Exception $error): void
{
/* Fire exceptions */
\call_user_func($this->callback,
call_user_func($this->callback,
$request,
$error
);
@@ -2,6 +2,7 @@
namespace Pecee\SimpleRouter\Handlers;
use Closure;
use Pecee\SimpleRouter\Event\EventArgument;
use Pecee\SimpleRouter\Router;
@@ -10,13 +11,13 @@ class DebugEventHandler implements IEventHandler
/**
* Debug callback
* @var \Closure
* @var Closure
*/
protected $callback;
protected Closure $callback;
public function __construct()
{
$this->callback = function (EventArgument $argument) {
$this->callback = static function (EventArgument $argument): void {
// todo: log in database
};
}
@@ -46,15 +47,15 @@ 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));
}
/**
* Set debug callback
*
* @param \Closure $event
* @param Closure $event
*/
public function setCallback(\Closure $event): void
public function setCallback(Closure $event): void
{
$this->callback = $event;
}
@@ -2,6 +2,7 @@
namespace Pecee\SimpleRouter\Handlers;
use Closure;
use Pecee\SimpleRouter\Event\EventArgument;
use Pecee\SimpleRouter\Router;
@@ -22,6 +23,11 @@ class EventHandler implements IEventHandler
*/
public const EVENT_LOAD = 'onLoad';
/**
* Fires when route is added to the router
*/
public const EVENT_ADD_ROUTE = 'onAddRoute';
/**
* Fires when a url-rewrite is and just before the routes are re-initialized.
*/
@@ -91,10 +97,11 @@ 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,
self::EVENT_ADD_ROUTE,
self::EVENT_REWRITE,
self::EVENT_BOOT,
self::EVENT_RENDER_BOOTMANAGER,
@@ -113,16 +120,16 @@ class EventHandler implements IEventHandler
* List of all registered events
* @var array
*/
private $registeredEvents = [];
private array $registeredEvents = [];
/**
* Register new event
*
* @param string $name
* @param \Closure $callback
* @param Closure $callback
* @return static
*/
public function register(string $name, \Closure $callback): IEventHandler
public function register(string $name, Closure $callback): IEventHandler
{
if (isset($this->registeredEvents[$name]) === true) {
$this->registeredEvents[$name][] = $callback;
@@ -137,7 +144,7 @@ class EventHandler implements IEventHandler
* Get events.
*
* @param string|null $name Filter events by name.
* @param array ...$names Add multiple names...
* @param array|string ...$names Add multiple names...
* @return array
*/
public function getEvents(?string $name, ...$names): array
@@ -169,7 +176,7 @@ class EventHandler implements IEventHandler
{
$events = $this->getEvents(static::EVENT_ALL, $name);
/* @var $event \Closure */
/* @var $event Closure */
foreach ($events as $event) {
$event(new EventArgument($name, $router, $eventArgs));
}
@@ -2,14 +2,15 @@
namespace Pecee\SimpleRouter\Handlers;
use Exception;
use Pecee\Http\Request;
interface IExceptionHandler
{
/**
* @param Request $request
* @param \Exception $error
* @param Exception $error
*/
public function handleError(Request $request, \Exception $error): void;
public function handleError(Request $request, Exception $error): void;
}
@@ -2,7 +2,7 @@
namespace Pecee\SimpleRouter\Route;
interface IControllerRoute extends IRoute
interface IControllerRoute extends ILoadableRoute
{
/**
* Get controller class-name
+25 -2
View File
@@ -29,7 +29,22 @@ interface IGroupRoute extends IRoute
* @param array $handlers
* @return static
*/
public function setExceptionHandlers(array $handlers);
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
@@ -53,13 +68,21 @@ interface IGroupRoute extends IRoute
*/
public function setDomains(array $domains): self;
/**
* Prepends prefix while ensuring that the url has the correct formatting.
*
* @param string $url
* @return static
*/
public function prependPrefix(string $url): self;
/**
* Set prefix that child-routes will inherit.
*
* @param string $prefix
* @return static
*/
public function setPrefix($prefix): self;
public function setPrefix(string $prefix): self;
/**
* Get prefix.
@@ -39,6 +39,13 @@ interface ILoadableRoute extends IRoute
*/
public function setUrl(string $url): self;
/**
* Prepends url while ensuring that the url has the correct formatting.
* @param string $url
* @return ILoadableRoute
*/
public function prependUrl(string $url): self;
/**
* Returns the provided name for the router.
*
@@ -75,6 +82,6 @@ interface ILoadableRoute extends IRoute
* @param string $regex
* @return static
*/
public function setMatch($regex): self;
public function setMatch(string $regex): self;
}
+20 -6
View File
@@ -10,11 +10,11 @@ interface IRoute
/**
* Method called to check if a domain matches
*
* @param string $route
* @param string $url
* @param Request $request
* @return bool
*/
public function matchRoute($route, Request $request): bool;
public function matchRoute(string $url, Request $request): bool;
/**
* Called when route is matched.
@@ -22,8 +22,8 @@ interface IRoute
*
* @param Request $request
* @param Router $router
* @throws \Pecee\SimpleRouter\Exceptions\NotFoundHttpException
* @return string
* @throws \Pecee\SimpleRouter\Exceptions\NotFoundHttpException
*/
public function renderRoute(Request $request, Router $router): ?string;
@@ -82,7 +82,7 @@ interface IRoute
/**
* Set callback
*
* @param string $callback
* @param string|array|\Closure $callback
* @return static
*/
public function setCallback($callback): self;
@@ -129,7 +129,7 @@ interface IRoute
* @param string $namespace
* @return static
*/
public function setDefaultNamespace($namespace): IRoute;
public function setDefaultNamespace(string $namespace): IRoute;
/**
* Get default namespace
@@ -196,7 +196,7 @@ interface IRoute
* @param string $middleware
* @return static
*/
public function addMiddleware($middleware): self;
public function addMiddleware(string $middleware): self;
/**
* Set middlewares array
@@ -206,4 +206,18 @@ interface IRoute
*/
public function setMiddlewares(array $middlewares): self;
/**
* If enabled parameters containing null-value will not be passed along to the callback.
*
* @param bool $enabled
* @return static $this
*/
public function setFilterEmptyParams(bool $enabled): self;
/**
* Status if filtering of empty params is enabled or disabled
* @return bool
*/
public function getFilterEmptyParams(): bool;
}
+68 -36
View File
@@ -6,20 +6,24 @@ use Pecee\Http\Middleware\IMiddleware;
use Pecee\Http\Request;
use Pecee\SimpleRouter\Exceptions\HttpException;
use Pecee\SimpleRouter\Router;
use Pecee\SimpleRouter\SimpleRouter;
abstract class LoadableRoute extends Route implements ILoadableRoute
{
/**
* @var string
*/
protected $url;
protected string $url;
/**
* @var string
*/
protected $name;
protected ?string $name = null;
protected $regex;
/**
* @var string|null
*/
protected ?string $regex = null;
/**
* Loads and renders middlewares-classes
@@ -34,7 +38,7 @@ abstract class LoadableRoute extends Route implements ILoadableRoute
foreach ($this->getMiddlewares() as $middleware) {
if (\is_object($middleware) === false) {
if (is_object($middleware) === false) {
$middleware = $router->getClassLoader()->loadClass($middleware);
}
@@ -42,7 +46,7 @@ abstract class LoadableRoute extends Route implements ILoadableRoute
throw new HttpException($middleware . ' must be inherit the IMiddleware interface');
}
$className = \get_class($middleware);
$className = get_class($middleware);
$router->debug('Loading middleware "%s"', $className);
$middleware->handle($request);
@@ -55,12 +59,18 @@ abstract class LoadableRoute extends Route implements ILoadableRoute
public function matchRegex(Request $request, $url): ?bool
{
/* Match on custom defined regular expression */
if ($this->regex === null) {
return null;
}
return ((bool)preg_match($this->regex, $request->getHost() . $url) !== false);
$parameters = [];
if ((bool)preg_match($this->regex, $url, $parameters) !== false) {
$this->setParameters($parameters);
return true;
}
return false;
}
/**
@@ -73,23 +83,49 @@ abstract class LoadableRoute extends Route implements ILoadableRoute
{
$this->url = ($url === '/') ? '/' : '/' . trim($url, '/') . '/';
$parameters = [];
if (strpos($this->url, $this->paramModifiers[0]) !== false) {
$regex = sprintf(static::PARAMETERS_REGEX_FORMAT, $this->paramModifiers[0], $this->paramOptionalSymbol, $this->paramModifiers[1]);
if ((bool)preg_match_all('/' . $regex . '/u', $this->url, $matches) !== false) {
$this->parameters = array_fill_keys($matches[1], null);
$parameters = array_fill_keys($matches[1], null);
}
}
$this->parameters = $parameters;
return $this;
}
/**
* Prepends url while ensuring that the url has the correct formatting.
*
* @param string $url
* @return ILoadableRoute
*/
public function prependUrl(string $url): ILoadableRoute
{
return $this->setUrl(rtrim($url, '/') . $this->url);
}
public function getUrl(): string
{
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.
@@ -103,15 +139,6 @@ abstract class LoadableRoute extends Route implements ILoadableRoute
{
$url = $this->getUrl();
$group = $this->getGroup();
if ($group !== null && \count($group->getDomains()) !== 0) {
$url = '//' . $group->getDomains()[0] . $url;
}
/* Contains parameters that aren't recognized and will be appended at the end of the url */
$unknownParams = [];
/* Create the param string - {parameter} */
$param1 = $this->paramModifiers[0] . '%s' . $this->paramModifiers[1];
@@ -124,29 +151,36 @@ abstract class LoadableRoute extends Route implements ILoadableRoute
foreach (array_keys($params) as $param) {
if ($parameters === '' || (\is_array($parameters) === true && \count($parameters) === 0)) {
if ($parameters === '' || (is_array($parameters) === true && count($parameters) === 0)) {
$value = '';
} else {
$p = (array)$parameters;
$value = array_key_exists($param, $p) ? $p[$param] : $params[$param];
/* If parameter is specifically set to null - use the original-defined value */
if ($value === null && isset($this->originalParameters[$param])) {
if ($value === null && isset($this->originalParameters[$param]) === true) {
$value = $this->originalParameters[$param];
}
}
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 {
$unknownParams[$param] = $value;
/* Parameter aren't recognized and will be appended at the end of the url */
$url .= $value . '/';
}
}
$url = '/' . ltrim($url, '/') . implode('/', $unknownParams);
$url = rtrim('/' . ltrim($url, '/'), '/') . '/';
return rtrim($url, '/') . '/';
$group = $this->getGroup();
if ($group !== null && count($group->getDomains()) !== 0 && SimpleRouter::request()->getHost() !== $group->getDomains()[0]) {
$url = '//' . $group->getDomains()[0] . $url;
}
return $url;
}
/**
@@ -154,7 +188,7 @@ abstract class LoadableRoute extends Route implements ILoadableRoute
*
* @return string
*/
public function getName(): string
public function getName(): ?string
{
return $this->name;
}
@@ -167,7 +201,7 @@ abstract class LoadableRoute extends Route implements ILoadableRoute
*/
public function hasName(string $name): bool
{
return strtolower($this->name) === strtolower($name);
return strtolower((string)$this->name) === strtolower($name);
}
/**
@@ -176,7 +210,7 @@ abstract class LoadableRoute extends Route implements ILoadableRoute
* @param string $regex
* @return static
*/
public function setMatch($regex): ILoadableRoute
public function setMatch(string $regex): ILoadableRoute
{
$this->regex = $regex;
@@ -197,9 +231,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
{
@@ -222,15 +256,15 @@ abstract class LoadableRoute extends Route implements ILoadableRoute
/**
* Merge with information from another route.
*
* @param array $values
* @param array $settings
* @param bool $merge
* @return static
*/
public function setSettings(array $values, bool $merge = false): IRoute
public function setSettings(array $settings, bool $merge = false): IRoute
{
if (isset($values['as']) === true) {
if (isset($settings['as']) === true) {
$name = $values['as'];
$name = $settings['as'];
if ($this->name !== null && $merge !== false) {
$name .= '.' . $this->name;
@@ -239,13 +273,11 @@ abstract class LoadableRoute extends Route implements ILoadableRoute
$this->setName($name);
}
if (isset($values['prefix']) === true) {
$this->setUrl($values['prefix'] . $this->getUrl());
if (isset($settings['prefix']) === true) {
$this->prependUrl($settings['prefix']);
}
parent::setSettings($values, $merge);
return $this;
return parent::setSettings($settings, $merge);
}
}
+193 -114
View File
@@ -2,31 +2,15 @@
namespace Pecee\SimpleRouter\Route;
use Pecee\Http\Middleware\IMiddleware;
use Pecee\Http\Request;
use Pecee\SimpleRouter\Exceptions\ClassNotFoundHttpException;
use Pecee\SimpleRouter\Exceptions\NotFoundHttpException;
use Pecee\SimpleRouter\Router;
abstract class Route implements IRoute
{
protected const PARAMETERS_REGEX_FORMAT = '%s([\w]+)(\%s?)%s';
protected const PARAMETERS_DEFAULT_REGEX = '[\w]+';
public const REQUEST_TYPE_GET = 'get';
public const REQUEST_TYPE_POST = 'post';
public const REQUEST_TYPE_PUT = 'put';
public const REQUEST_TYPE_PATCH = 'patch';
public const REQUEST_TYPE_OPTIONS = 'options';
public const REQUEST_TYPE_DELETE = 'delete';
public static $requestTypes = [
self::REQUEST_TYPE_GET,
self::REQUEST_TYPE_POST,
self::REQUEST_TYPE_PUT,
self::REQUEST_TYPE_PATCH,
self::REQUEST_TYPE_OPTIONS,
self::REQUEST_TYPE_DELETE,
];
protected const PARAMETERS_DEFAULT_REGEX = '[\w-]+';
/**
* If enabled parameters containing null-value
@@ -34,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 +60,7 @@ abstract class Route implements IRoute
*/
public function renderRoute(Request $request, Router $router): ?string
{
$router->debug('Starting rendering route "%s"', \get_class($this));
$router->debug('Starting rendering route "%s"', get_class($this));
$callback = $this->getCallback();
@@ -76,66 +69,82 @@ abstract class Route implements IRoute
}
$router->debug('Parsing parameters');
$parameters = $this->getParameters();
$router->debug('Finished parsing parameters');
/* Filter parameters with null-value */
if ($this->filterEmptyParams === true) {
$parameters = array_filter($parameters, function ($var) {
$parameters = array_filter($parameters, static function ($var): bool {
return ($var !== null);
});
}
/* Render callback function */
if (\is_callable($callback) === true) {
if (is_callable($callback) === true) {
$router->debug('Executing callback');
/* Load class from type hinting */
if (is_array($callback) === true && isset($callback[0], $callback[1]) === true) {
$callback[0] = $router->getClassLoader()->loadClass($callback[0]);
}
/* When the callback is a function */
return $router->getClassLoader()->loadClosure($callback, $parameters);
}
/* When the callback is a class + method */
$controller = explode('@', $callback);
$controller = $this->getClass();
$method = $this->getMethod();
$namespace = $this->getNamespace();
$className = ($namespace !== null && $controller[0][0] !== '\\') ? $namespace . '\\' . $controller[0] : $controller[0];
$className = ($namespace !== null && $controller[0] !== '\\') ? $namespace . '\\' . $controller : $controller;
$router->debug('Loading class %s', $className);
$class = $router->getClassLoader()->loadClass($className);
$method = $controller[1];
if (method_exists($class, $method) === false) {
throw new NotFoundHttpException(sprintf('Method "%s" does not exist in class "%s"', $method, $className), 404);
if ($method === null) {
$controller[1] = '__invoke';
}
$router->debug('Executing callback');
if (method_exists($class, $method) === false) {
throw new ClassNotFoundHttpException($className, $method, sprintf('Method "%s" does not exist in class "%s"', $method, $className), 404, null);
}
return \call_user_func_array([$class, $method], $parameters);
$router->debug('Executing callback %s -> %s', $className, $method);
return $router->getClassLoader()->loadClassMethod($class, $method, $parameters);
}
protected function parseParameters($route, $url, $parameterRegex = null)
protected function parseParameters($route, $url, Request $request, $parameterRegex = null): ?array
{
$regex = sprintf(static::PARAMETERS_REGEX_FORMAT, $this->paramModifiers[0], $this->paramOptionalSymbol, $this->paramModifiers[1]);
$parameters = [];
$regex = (strpos($route, $this->paramModifiers[0]) === false) ? null :
sprintf
(
static::PARAMETERS_REGEX_FORMAT,
$this->paramModifiers[0],
$this->paramOptionalSymbol,
$this->paramModifiers[1]
);
// Ensures that host names/domains will work with parameters
$url = '/' . ltrim($url, '/');
if ($route[0] === $this->paramModifiers[0]) {
$url = '/' . ltrim($url, '/');
}
if ((bool)preg_match_all('/' . $regex . '/u', $route, $parameters) === false) {
$urlRegex = '';
$parameters = [];
if ($regex === null || (bool)preg_match_all('/' . $regex . '/u', $route, $parameters) === false) {
$urlRegex = preg_quote($route, '/');
} else {
$urlParts = preg_split('/((\-?\/?)\{[^}]+\})/', $route);
foreach ($urlParts as $key => $t) {
foreach (preg_split('/((\.?-?\/?){[^' . $this->paramModifiers[1] . ']+' . $this->paramModifiers[1] . ')/', $route) as $key => $t) {
$regex = '';
if ($key < \count($parameters[1])) {
if ($key < count($parameters[1])) {
$name = $parameters[1][$key];
@@ -143,26 +152,18 @@ abstract class Route implements IRoute
if (isset($this->where[$name]) === true) {
$regex = $this->where[$name];
} else {
/* If method specific regex is defined use that, otherwise use the default parameter regex */
if ($parameterRegex !== null) {
$regex = $parameterRegex;
} else {
$regex = $this->defaultParameterRegex ?? static::PARAMETERS_DEFAULT_REGEX;
}
$regex = $parameterRegex ?? $this->defaultParameterRegex ?? static::PARAMETERS_DEFAULT_REGEX;
}
$regex = sprintf('(?:\/|\-)%1$s(?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);
}
$urlParts[$key] = preg_quote($t, '/') . $regex;
$urlRegex .= preg_quote($t, '/') . $regex;
}
$urlRegex = implode('', $urlParts);
}
if ((bool)preg_match(sprintf($this->urlRegex, $urlRegex), $url, $matches) === false) {
// Get name of last param
if (trim($urlRegex) === '' || (bool)preg_match(sprintf($this->urlRegex, $urlRegex), $url, $matches) === false) {
return null;
}
@@ -170,12 +171,34 @@ abstract class Route implements IRoute
if (isset($parameters[1]) === true) {
$groupParameters = $this->getGroup() !== null ? $this->getGroup()->getParameters() : [];
$lastParams = [];
/* Only take matched parameters with name */
foreach ((array)$parameters[1] as $name) {
$values[$name] = (isset($matches[$name]) && $matches[$name] !== '') ? $matches[$name] : null;
$originalPath = $request->getUrl()->getOriginalPath();
foreach ((array)$parameters[1] as $i => $name) {
// Ignore parent parameters
if (isset($groupParameters[$name]) === true) {
$lastParams[$name] = $matches[$name];
continue;
}
// If last parameter 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 += $lastParams;
}
$this->originalParameters = $values;
return $values;
}
@@ -188,11 +211,11 @@ abstract class Route implements IRoute
*/
public function getIdentifier(): string
{
if (\is_string($this->callback) === true && strpos($this->callback, '@') !== false) {
if (is_string($this->callback) === true && strpos($this->callback, '@') !== false) {
return $this->callback;
}
return 'function_' . md5($this->callback);
return 'function:' . md5($this->callback);
}
/**
@@ -247,9 +270,8 @@ abstract class Route implements IRoute
$this->group = $group;
/* Add/merge parent settings with child */
$this->setSettings($group->toArray(), true);
return $this;
return $this->setSettings($group->toArray(), true);
}
/**
@@ -268,7 +290,7 @@ abstract class Route implements IRoute
/**
* Set callback
*
* @param string $callback
* @param string|array|\Closure $callback
* @return static
*/
public function setCallback($callback): IRoute
@@ -279,7 +301,7 @@ abstract class Route implements IRoute
}
/**
* @return string|callable
* @return string|callable|null
*/
public function getCallback()
{
@@ -288,7 +310,11 @@ abstract class Route implements IRoute
public function getMethod(): ?string
{
if (\is_string($this->callback) === true && strpos($this->callback, '@') !== false) {
if (is_array($this->callback) === true && count($this->callback) > 1) {
return $this->callback[1];
}
if (is_string($this->callback) === true && strpos($this->callback, '@') !== false) {
$tmp = explode('@', $this->callback);
return $tmp[1];
@@ -299,7 +325,11 @@ abstract class Route implements IRoute
public function getClass(): ?string
{
if (\is_string($this->callback) === true && strpos($this->callback, '@') !== false) {
if (is_array($this->callback) === true && count($this->callback) > 0) {
return $this->callback[0];
}
if (is_string($this->callback) === true && strpos($this->callback, '@') !== false) {
$tmp = explode('@', $this->callback);
return $tmp[0];
@@ -310,14 +340,14 @@ abstract class Route implements IRoute
public function setMethod(string $method): IRoute
{
$this->callback = sprintf('%s@%s', $this->getClass(), $method);
$this->callback = [$this->getClass(), $method];
return $this;
}
public function setClass(string $class): IRoute
{
$this->callback = sprintf('%s@%s', $class, $this->getMethod());
$this->callback = [$class, $this->getMethod()];
return $this;
}
@@ -328,6 +358,22 @@ 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) {
// Don't overwrite namespaces that starts with \
if ($ns[0] !== '\\') {
$namespace .= '\\' . $ns;
} else {
$namespace = $ns;
}
}
$this->namespace = $namespace;
return $this;
@@ -337,7 +383,7 @@ abstract class Route implements IRoute
* @param string $namespace
* @return static
*/
public function setDefaultNamespace($namespace): IRoute
public function setDefaultNamespace(string $namespace): IRoute
{
$this->defaultNamespace = $namespace;
@@ -357,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.
*
@@ -370,15 +427,15 @@ abstract class Route implements IRoute
$values['namespace'] = $this->namespace;
}
if (\count($this->requestMethods) !== 0) {
if (count($this->requestMethods) !== 0) {
$values['method'] = $this->requestMethods;
}
if (\count($this->where) !== 0) {
if (count($this->where) !== 0) {
$values['where'] = $this->where;
}
if (\count($this->middlewares) !== 0) {
if (count($this->middlewares) !== 0) {
$values['middleware'] = $this->middlewares;
}
@@ -386,41 +443,49 @@ abstract class Route implements IRoute
$values['defaultParameterRegex'] = $this->defaultParameterRegex;
}
if ($this->slashParameterEnabled === true) {
$values['includeSlash'] = $this->slashParameterEnabled;
}
return $values;
}
/**
* Merge with information from another route.
*
* @param array $values
* @param array $settings
* @param bool $merge
* @return static
*/
public function setSettings(array $values, bool $merge = false): IRoute
public function setSettings(array $settings, bool $merge = false): IRoute
{
if ($this->namespace === null && isset($values['namespace']) === true) {
$this->setNamespace($values['namespace']);
if (isset($settings['namespace']) === true) {
$this->setNamespace($settings['namespace']);
}
if (isset($values['method']) === true) {
$this->setRequestMethods(array_merge($this->requestMethods, (array)$values['method']));
if (isset($settings['method']) === true) {
$this->setRequestMethods(array_merge($this->requestMethods, (array)$settings['method']));
}
if (isset($values['where']) === true) {
$this->setWhere(array_merge($this->where, (array)$values['where']));
if (isset($settings['where']) === true) {
$this->setWhere(array_merge($this->where, (array)$settings['where']));
}
if (isset($values['parameters']) === true) {
$this->setParameters(array_merge($this->parameters, (array)$values['parameters']));
if (isset($settings['parameters']) === true) {
$this->setParameters(array_merge($this->parameters, (array)$settings['parameters']));
}
// Push middleware if multiple
if (isset($values['middleware']) === true) {
$this->setMiddlewares(array_merge((array)$values['middleware'], $this->middlewares));
if (isset($settings['middleware']) === true) {
$this->setMiddlewares(array_merge((array)$settings['middleware'], $this->middlewares));
}
if (isset($values['defaultParameterRegex']) === true) {
$this->setDefaultParameterRegex($values['defaultParameterRegex']);
if (isset($settings['defaultParameterRegex']) === true) {
$this->setDefaultParameterRegex($settings['defaultParameterRegex']);
}
if (isset($settings['includeSlash']) === true) {
$this->setSlashParameterEnabled($settings['includeSlash']);
}
return $this;
@@ -453,9 +518,9 @@ abstract class Route implements IRoute
* Add regular expression parameter match.
* Alias for LoadableRoute::where()
*
* @see LoadableRoute::where()
* @param array $options
* @return static
* @see LoadableRoute::where()
*/
public function where(array $options)
{
@@ -472,7 +537,7 @@ abstract class Route implements IRoute
/* Sort the parameters after the user-defined param order, if any */
$parameters = [];
if (\count($this->originalParameters) !== 0) {
if (count($this->originalParameters) !== 0) {
$parameters = $this->originalParameters;
}
@@ -487,14 +552,6 @@ abstract class Route implements IRoute
*/
public function setParameters(array $parameters): IRoute
{
/*
* If this is the first time setting parameters we store them so we
* later can organize the array, in case somebody tried to sort the array.
*/
if (\count($parameters) !== 0 && \count($this->originalParameters) === 0) {
$this->originalParameters = $parameters;
}
$this->parameters = array_merge($this->parameters, $parameters);
return $this;
@@ -503,11 +560,11 @@ abstract class Route implements IRoute
/**
* Add middleware class-name
*
* @deprecated This method is deprecated and will be removed in the near future.
* @param IMiddleware|string $middleware
* @param string $middleware
* @return static
* @deprecated This method is deprecated and will be removed in the near future.
*/
public function setMiddleware($middleware)
public function setMiddleware(string $middleware): self
{
$this->middlewares[] = $middleware;
@@ -517,10 +574,10 @@ abstract class Route implements IRoute
/**
* Add middleware class-name
*
* @param IMiddleware|string $middleware
* @param string $middleware
* @return static
*/
public function addMiddleware($middleware): IRoute
public function addMiddleware(string $middleware): IRoute
{
$this->middlewares[] = $middleware;
@@ -555,7 +612,7 @@ abstract class Route implements IRoute
* @param string $regex
* @return static
*/
public function setDefaultParameterRegex($regex)
public function setDefaultParameterRegex(string $regex): self
{
$this->defaultParameterRegex = $regex;
@@ -572,4 +629,26 @@ abstract class Route implements IRoute
return $this->defaultParameterRegex;
}
/**
* If enabled parameters containing null-value will not be passed along to the callback.
*
* @param bool $enabled
* @return static $this
*/
public function setFilterEmptyParams(bool $enabled): IRoute
{
$this->filterEmptyParams = $enabled;
return $this;
}
/**
* Status if filtering of empty params is enabled or disabled
* @return bool
*/
public function getFilterEmptyParams(): bool
{
return $this->filterEmptyParams;
}
}
@@ -3,13 +3,14 @@
namespace Pecee\SimpleRouter\Route;
use Pecee\Http\Request;
use Pecee\SimpleRouter\SimpleRouter;
class RouteController extends LoadableRoute implements IControllerRoute
{
protected $defaultMethod = 'index';
protected $controller;
protected $method;
protected $names = [];
protected string $defaultMethod = 'index';
protected string $controller;
protected ?string $method = null;
protected array $names = [];
public function __construct($url, $controller)
{
@@ -35,7 +36,7 @@ class RouteController extends LoadableRoute implements IControllerRoute
$method = substr($name, strrpos($name, '.') + 1);
$newName = substr($name, 0, strrpos($name, '.'));
if (\in_array($method, $this->names, true) === true && strtolower($this->name) === strtolower($newName)) {
if (in_array($method, $this->names, true) === true && strtolower($this->name) === strtolower($newName)) {
return true;
}
}
@@ -52,7 +53,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;
}
@@ -64,10 +65,10 @@ class RouteController extends LoadableRoute implements IControllerRoute
if ($method !== null) {
/* Remove requestType from method-name, if it exists */
foreach (static::$requestTypes as $requestType) {
foreach (Request::$requestTypes as $requestType) {
if (stripos($method, $requestType) === 0) {
$method = (string)substr($method, \strlen($requestType));
$method = substr($method, strlen($requestType));
break;
}
}
@@ -77,40 +78,42 @@ class RouteController extends LoadableRoute implements IControllerRoute
$group = $this->getGroup();
if ($group !== null && \count($group->getDomains()) !== 0) {
$url .= '//' . $group->getDomains()[0];
$url .= '/' . trim($this->getUrl(), '/') . '/' . strtolower((string)$method) . implode('/', $parameters);
$url = '/' . trim($url, '/') . '/';
if ($group !== null && count($group->getDomains()) !== 0 && SimpleRouter::request()->getHost() !== $group->getDomains()[0]) {
$url = '//' . $group->getDomains()[0] . $url;
}
$url .= '/' . trim($this->getUrl(), '/') . '/' . strtolower($method) . implode('/', $parameters);
return '/' . trim($url, '/') . '/';
return $url;
}
public function matchRoute($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;
}
/* Match global regular-expression for route */
$regexMatch = $this->matchRegex($request, $url);
if ($regexMatch === false || (stripos($url, $this->url) !== 0 && strtolower($url) !== strtolower($this->url))) {
if ($regexMatch === false || (stripos($url, $this->url) !== 0 && strtoupper($url) !== strtoupper($this->url))) {
return false;
}
$strippedUrl = trim(str_ireplace($this->url, '/', $url), '/');
$path = explode('/', $strippedUrl);
if (\count($path) !== 0) {
if (count($path) !== 0) {
$method = (isset($path[0]) === false || trim($path[0]) === '') ? $this->defaultMethod : $path[0];
$this->method = $request->getMethod() . ucfirst($method);
$this->parameters = \array_slice($path, 1);
$this->parameters = array_slice($path, 1);
// Set callback
$this->setCallback($this->controller . '@' . $this->method);
$this->setCallback([$this->controller, $this->method]);
return true;
}
@@ -167,19 +170,17 @@ class RouteController extends LoadableRoute implements IControllerRoute
/**
* Merge with information from another route.
*
* @param array $values
* @param array $settings
* @param bool $merge
* @return static
*/
public function setSettings(array $values, bool $merge = false): IRoute
public function setSettings(array $settings, bool $merge = false): IRoute
{
if (isset($values['names']) === true) {
$this->names = $values['names'];
if (isset($settings['names']) === true) {
$this->names = $settings['names'];
}
parent::setSettings($values, $merge);
return $this;
return parent::setSettings($settings, $merge);
}
}
+85 -25
View File
@@ -7,10 +7,12 @@ use Pecee\SimpleRouter\Handlers\IExceptionHandler;
class RouteGroup extends Route implements IGroupRoute
{
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
@@ -20,16 +22,20 @@ 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;
}
foreach ($this->domains as $domain) {
$parameters = $this->parseParameters($domain, $request->getHost(), '.*');
// If domain has no parameters but matches
if ($domain === $request->getHost()) {
return true;
}
if ($parameters !== null && \count($parameters) !== 0) {
$parameters = $this->parseParameters($domain, $request->getHost(), $request, '.*');
if ($parameters !== null && count($parameters) !== 0) {
$this->parameters = $parameters;
return true;
@@ -46,14 +52,33 @@ class RouteGroup extends Route implements IGroupRoute
* @param Request $request
* @return bool
*/
public function matchRoute($url, Request $request): bool
public function matchRoute(string $url, Request $request): bool
{
if ($this->getGroup() !== null && $this->getGroup()->matchRoute($url, $request) === false) {
return false;
}
if ($this->prefix !== null) {
/* Parse parameters from current route */
$parameters = $this->parseParameters($this->prefix, $url, $request);
/* If no custom regular expression or parameters was found on this route, we stop */
if ($parameters === null) {
return false;
}
/* Set the parameters */
$this->setParameters($parameters);
}
$parsedPrefix = $this->prefix;
foreach ($this->getParameters() as $parameter => $value) {
$parsedPrefix = str_ireplace('{' . $parameter . '}', (string)$value, (string)$parsedPrefix);
}
/* Skip if prefix doesn't match */
if ($this->prefix !== null && stripos($url, $this->prefix) === false) {
if ($this->prefix !== null && stripos($url, rtrim($parsedPrefix, '/') . '/') === false) {
return false;
}
@@ -123,13 +148,24 @@ class RouteGroup extends Route implements IGroupRoute
* @param string $prefix
* @return static
*/
public function setPrefix($prefix): IGroupRoute
public function setPrefix(string $prefix): IGroupRoute
{
$this->prefix = '/' . trim($prefix, '/');
return $this;
}
/**
* Prepends prefix while ensuring that the url has the correct formatting.
*
* @param string $url
* @return static
*/
public function prependPrefix(string $url): IGroupRoute
{
return $this->setPrefix(rtrim($url, '/') . $this->prefix);
}
/**
* Set prefix that child-routes will inherit.
*
@@ -141,30 +177,56 @@ class RouteGroup extends Route implements IGroupRoute
}
/**
* Merge with information from another route.
* When enabled group will overwrite any existing exception-handlers.
*
* @param array $values
* @param bool $merge
* @return static
*/
public function setSettings(array $values, bool $merge = false): IRoute
public function setMergeExceptionHandlers(bool $merge): IGroupRoute
{
$this->mergeExceptionHandlers = $merge;
if (isset($values['prefix']) === true) {
$this->setPrefix($values['prefix'] . $this->prefix);
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.
*
* @param array $settings
* @param bool $merge
* @return static
*/
public function setSettings(array $settings, bool $merge = false): IRoute
{
if (isset($settings['prefix']) === true) {
$this->setPrefix($settings['prefix'] . $this->prefix);
}
if ($merge === false && isset($values['exceptionHandler']) === true) {
$this->setExceptionHandlers((array)$values['exceptionHandler']);
if (isset($settings['mergeExceptionHandlers']) === true) {
$this->setMergeExceptionHandlers($settings['mergeExceptionHandlers']);
}
if ($merge === false && isset($values['domain']) === true) {
$this->setDomains((array)$values['domain']);
if ($merge === false && isset($settings['exceptionHandler']) === true) {
$this->setExceptionHandlers((array)$settings['exceptionHandler']);
}
if (isset($values['as']) === true) {
if (isset($settings['domain']) === true) {
$this->setDomains((array)$settings['domain']);
}
$name = $values['as'];
if (isset($settings['as']) === true) {
$name = $settings['as'];
if ($this->name !== null && $merge !== false) {
$name .= '.' . $this->name;
@@ -173,9 +235,7 @@ class RouteGroup extends Route implements IGroupRoute
$this->name = $name;
}
parent::setSettings($values, $merge);
return $this;
return parent::setSettings($settings, $merge);
}
/**
@@ -195,7 +255,7 @@ class RouteGroup extends Route implements IGroupRoute
$values['as'] = $this->name;
}
if (\count($this->parameters) !== 0) {
if (count($this->parameters) !== 0) {
$values['parameters'] = $this->parameters;
}
@@ -1,47 +1,7 @@
<?php
namespace Pecee\SimpleRouter\Route;
use Pecee\Http\Request;
class RoutePartialGroup extends RouteGroup implements IPartialGroupRoute
{
/**
* RoutePartialGroup constructor.
*/
public function __construct()
{
$this->urlRegex = '/^%s\/?/u';
}
/**
* Method called to check if route matches
*
* @param string $url
* @param Request $request
* @return bool
*/
public function matchRoute($url, Request $request): bool
{
if ($this->getGroup() !== null && $this->getGroup()->matchRoute($url, $request) === false) {
return false;
}
if ($this->prefix !== null) {
/* Parse parameters from current route */
$parameters = $this->parseParameters($this->prefix, $url);
/* If no custom regular expression or parameters was found on this route, we stop */
if ($parameters === null) {
return false;
}
/* Set the parameters */
$this->setParameters((array)$parameters);
}
return $this->matchDomain($request);
}
}
+63 -50
View File
@@ -3,31 +3,32 @@
namespace Pecee\SimpleRouter\Route;
use Pecee\Http\Request;
use Pecee\SimpleRouter\SimpleRouter;
class RouteResource extends LoadableRoute implements IControllerRoute
{
protected $urls = [
'index' => '',
'create' => 'create',
'store' => '',
'show' => '',
'edit' => 'edit',
'update' => '',
protected array $urls = [
'index' => '',
'create' => 'create',
'store' => '',
'show' => '',
'edit' => 'edit',
'update' => '',
'destroy' => '',
];
protected $methodNames = [
'index' => 'index',
'create' => 'create',
'store' => 'store',
'show' => 'show',
'edit' => 'edit',
'update' => 'update',
protected array $methodNames = [
'index' => 'index',
'create' => 'create',
'store' => 'store',
'show' => 'show',
'edit' => 'edit',
'update' => 'update',
'destroy' => 'destroy',
];
protected $names = [];
protected $controller;
protected array $names = [];
protected string $controller;
public function __construct($url, $controller)
{
@@ -54,7 +55,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,45 +69,59 @@ 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 = array_search($name, $this->names, true);
$parametersUrl = '';
if ($parameters !== null && count($parameters) > 0) {
$parametersUrl = join('/', $parameters) . '/';
}
return $this->url;
if ($url !== false) {
return rtrim($this->url . $parametersUrl . $this->urls[$url], '/') . '/';
}
$url = $this->url . $parametersUrl;
$group = $this->getGroup();
if ($group !== null && count($group->getDomains()) !== 0 && SimpleRouter::request()->getHost() !== $group->getDomains()[0]) {
$url = '//' . $group->getDomains()[0] . $url;
}
return $url;
}
protected function call($method)
protected function call($method): bool
{
$this->setCallback($this->controller . '@' . $method);
$this->setCallback([$this->controller, $method]);
return true;
}
public function matchRoute($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;
}
/* Match global regular-expression for route */
$regexMatch = $this->matchRegex($request, $url);
if ($regexMatch === false || (stripos($url, $this->url) !== 0 && strtolower($url) !== strtolower($this->url))) {
if ($regexMatch === false || (stripos($url, $this->url) !== 0 && strtoupper($url) !== strtoupper($this->url))) {
return false;
}
$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
@@ -115,32 +130,32 @@ class RouteResource extends LoadableRoute implements IControllerRoute
$method = $request->getMethod();
// Delete
if ($method === static::REQUEST_TYPE_DELETE && $id !== null) {
if ($method === Request::REQUEST_TYPE_DELETE && $id !== null) {
return $this->call($this->methodNames['destroy']);
}
// Update
if ($id !== null && \in_array($method, [static::REQUEST_TYPE_PATCH, static::REQUEST_TYPE_PUT], true) === true) {
if ($id !== null && in_array($method, [Request::REQUEST_TYPE_PATCH, Request::REQUEST_TYPE_PUT], true) === true) {
return $this->call($this->methodNames['update']);
}
// Edit
if ($method === static::REQUEST_TYPE_GET && $id !== null && $action === 'edit') {
if ($method === Request::REQUEST_TYPE_GET && $id !== null && $action === 'edit') {
return $this->call($this->methodNames['edit']);
}
// Create
if ($method === static::REQUEST_TYPE_GET && $id === 'create') {
if ($method === Request::REQUEST_TYPE_GET && $id === 'create') {
return $this->call($this->methodNames['create']);
}
// Save
if ($method === static::REQUEST_TYPE_POST) {
if ($method === Request::REQUEST_TYPE_POST) {
return $this->call($this->methodNames['store']);
}
// Show
if ($method === static::REQUEST_TYPE_GET && $id !== null) {
if ($method === Request::REQUEST_TYPE_GET && $id !== null) {
return $this->call($this->methodNames['show']);
}
@@ -172,12 +187,12 @@ class RouteResource extends LoadableRoute implements IControllerRoute
$this->name = $name;
$this->names = [
'index' => $this->name . '.index',
'create' => $this->name . '.create',
'store' => $this->name . '.store',
'show' => $this->name . '.show',
'edit' => $this->name . '.edit',
'update' => $this->name . '.update',
'index' => $this->name . '.index',
'create' => $this->name . '.create',
'store' => $this->name . '.store',
'show' => $this->name . '.show',
'edit' => $this->name . '.edit',
'update' => $this->name . '.update',
'destroy' => $this->name . '.destroy',
];
@@ -190,7 +205,7 @@ class RouteResource extends LoadableRoute implements IControllerRoute
* @param array $names
* @return static $this
*/
public function setMethodNames(array $names)
public function setMethodNames(array $names): RouteResource
{
$this->methodNames = $names;
@@ -210,23 +225,21 @@ class RouteResource extends LoadableRoute implements IControllerRoute
/**
* Merge with information from another route.
*
* @param array $values
* @param array $settings
* @param bool $merge
* @return static
*/
public function setSettings(array $values, bool $merge = false): IRoute
public function setSettings(array $settings, bool $merge = false): IRoute
{
if (isset($values['names']) === true) {
$this->names = $values['names'];
if (isset($settings['names']) === true) {
$this->names = $settings['names'];
}
if (isset($values['methods']) === true) {
$this->methodNames = $values['methods'];
if (isset($settings['methods']) === true) {
$this->methodNames = $settings['methods'];
}
parent::setSettings($values, $merge);
return $this;
return parent::setSettings($settings, $merge);
}
}
+8 -3
View File
@@ -6,13 +6,18 @@ 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);
}
public function matchRoute($url, Request $request): bool
public function matchRoute(string $url, Request $request): bool
{
if ($this->getGroup() !== null && $this->getGroup()->matchRoute($url, $request) === false) {
return false;
@@ -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) {
+191 -146
View File
@@ -2,6 +2,7 @@
namespace Pecee\SimpleRouter;
use Exception;
use Pecee\Exceptions\InvalidArgumentException;
use Pecee\Http\Exceptions\MalformedUrlException;
use Pecee\Http\Middleware\BaseCsrfVerifier;
@@ -27,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.
@@ -78,37 +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 bool $renderMultipleRoutes = false;
/**
* Router constructor.
@@ -151,18 +165,21 @@ class Router
*/
public function addRoute(IRoute $route): IRoute
{
$this->fireEvents(EventHandler::EVENT_ADD_ROUTE, [
'route' => $route,
'isSubRoute' => $this->isProcessingRoute,
]);
/*
* If a route is currently being processed, that means that the route being added are rendered from the parent
* routes callback, so we add them to the stack instead.
*/
if ($this->isProcessingRoute === true) {
$this->routeStack[] = $route;
return $route;
} else {
$this->routes[] = $route;
}
$this->routes[] = $route;
return $route;
}
@@ -174,26 +191,25 @@ class Router
*/
protected function renderAndProcess(IRoute $route): void
{
$this->isProcessingRoute = true;
$route->renderRoute($this->request, $this);
$this->isProcessingRoute = false;
if (\count($this->routeStack) !== 0) {
if (count($this->routeStack) !== 0) {
/* Pop and grab the routes added when executing group callback earlier */
$stack = $this->routeStack;
$this->routeStack = [];
/* Route any routes added to the stack */
$this->processRoutes($stack, $route);
$this->processRoutes($stack, ($route instanceof IGroupRoute) ? $route : null);
}
}
/**
* Process added routes.
*
* @param array $routes
* @param array|IRoute[] $routes
* @param IGroupRoute|null $group
* @throws NotFoundHttpException
*/
@@ -201,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;
@@ -213,10 +226,10 @@ 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));
$this->debug('Processing route "%s"', get_class($route));
if ($group !== null) {
/* Add the parent group */
@@ -229,14 +242,23 @@ class Router
if ($route->matchRoute($url, $this->request) === true) {
/* Add exception handlers */
if (\count($route->getExceptionHandlers()) !== 0) {
/** @noinspection AdditionOperationOnArraysInspection */
$exceptionHandlers += $route->getExceptionHandlers();
if (count($route->getExceptionHandlers()) !== 0) {
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;
}
}
@@ -254,19 +276,24 @@ class Router
$this->processedRoutes[] = $route;
}
}
$this->exceptionHandlers = array_merge($exceptionHandlers, $this->exceptionHandlers);
}
/**
* Load routes
* @throws NotFoundHttpException
* @return void
* @throws NotFoundHttpException
*/
public function loadRoutes(): void
{
$this->debug('Loading routes');
$this->fireEvents(EventHandler::EVENT_LOAD_ROUTES, [
'routes' => $this->routes,
]);
/* Loop through each route-request */
$this->processRoutes($this->routes);
$this->fireEvents(EventHandler::EVENT_BOOT, [
'bootmanagers' => $this->bootManagers,
]);
@@ -276,11 +303,11 @@ class Router
/* @var $manager IRouterBootManager */
foreach ($this->bootManagers as $manager) {
$className = \get_class($manager);
$className = get_class($manager);
$this->debug('Rendering bootmanager "%s"', $className);
$this->fireEvents(EventHandler::EVENT_RENDER_BOOTMANAGER, [
'bootmanagers' => $this->bootManagers,
'bootmanager' => $manager,
'bootmanager' => $manager,
]);
/* Render bootmanager */
@@ -289,13 +316,6 @@ class Router
$this->debug('Finished rendering bootmanager "%s"', $className);
}
$this->fireEvents(EventHandler::EVENT_LOAD_ROUTES, [
'routes' => $this->routes,
]);
/* Loop through each route-request */
$this->processRoutes($this->routes);
$this->debug('Finished loading routes');
}
@@ -303,10 +323,10 @@ class Router
* Start the routing
*
* @return string|null
* @throws \Pecee\SimpleRouter\Exceptions\NotFoundHttpException
* @throws NotFoundHttpException
* @throws \Pecee\Http\Middleware\Exceptions\TokenMismatchException
* @throws HttpException
* @throws \Exception
* @throws Exception
*/
public function start(): ?string
{
@@ -322,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();
@@ -342,13 +366,13 @@ class Router
*
* @return string|null
* @throws HttpException
* @throws \Exception
* @throws Exception
*/
public function routeRequest(): ?string
{
$this->debug('Routing request');
$methodNotAllowed = false;
$methodNotAllowed = null;
try {
$url = $this->request->getRewriteUrl() ?? $this->request->getUrl()->getPath();
@@ -356,7 +380,10 @@ class Router
/* @var $route ILoadableRoute */
foreach ($this->processedRoutes as $key => $route) {
$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 ($route->matchRoute($url, $this->request) === true) {
@@ -366,14 +393,19 @@ class Router
]);
/* Check if request method matches */
if (\count($route->getRequestMethods()) !== 0 && \in_array($this->request->getMethod(), $route->getRequestMethods(), true) === false) {
if (count($route->getRequestMethods()) !== 0 && in_array($this->request->getMethod(), $route->getRequestMethods(), true) === false) {
$this->debug('Method "%s" not allowed', $this->request->getMethod());
$methodNotAllowed = true;
// Only set method not allowed is not already set
if ($methodNotAllowed === null) {
$methodNotAllowed = true;
}
continue;
}
$this->fireEvents(EventHandler::EVENT_RENDER_MIDDLEWARES, [
'route' => $route,
'route' => $route,
'middlewares' => $route->getMiddlewares(),
]);
@@ -392,28 +424,35 @@ class Router
'route' => $route,
]);
$output = $route->renderRoute($this->request, $this);
if ($output !== null) {
return $output;
}
$routeOutput = $route->renderRoute($this->request, $this);
$output = $this->handleRouteRewrite($key, $url);
if ($output !== null) {
return $output;
if ($this->renderMultipleRoutes === true) {
if ($routeOutput !== '') {
return $routeOutput;
}
$output = $this->handleRouteRewrite($key, $url);
if ($output !== null) {
return $output;
}
} else {
$output = $this->handleRouteRewrite($key, $url);
return $output ?? $routeOutput;
}
}
}
} catch (\Exception $e) {
$this->handleException($e);
} catch (Exception $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 HttpException($message, 403));
return $this->handleException(new NotFoundHttpException($message, 403));
}
if (\count($this->request->getLoadedRoutes()) === 0) {
if (count($this->request->getLoadedRoutes()) === 0) {
$rewriteUrl = $this->request->getRewriteUrl();
@@ -438,9 +477,9 @@ class Router
* @param string $url
* @return string|null
* @throws HttpException
* @throws \Exception
* @throws Exception
*/
protected function handleRouteRewrite($key, string $url): ?string
protected function handleRouteRewrite(string $key, string $url): ?string
{
/* If the request has changed */
if ($this->request->hasPendingRewrite() === false) {
@@ -455,11 +494,13 @@ class Router
}
if ($this->request->getRewriteUrl() !== $url) {
unset($this->processedRoutes[$key]);
$this->request->setHasPendingRewrite(false);
$this->fireEvents(EventHandler::EVENT_REWRITE, [
'rewriteUrl' => $this->request->getRewriteUrl(),
'rewriteUrl' => $this->request->getRewriteUrl(),
'rewriteRoute' => $this->request->getRewriteRoute(),
]);
@@ -470,59 +511,63 @@ class Router
}
/**
* @param \Exception $e
* @throws HttpException
* @throws \Exception
* @param Exception $e
* @return string|null
* @throws Exception
* @throws HttpException
*/
protected function handleException(\Exception $e): ?string
protected function handleException(Exception $e): ?string
{
$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, [
'exception' => $e,
'exception' => $e,
'exceptionHandlers' => $this->exceptionHandlers,
]);
/* @var $handler IExceptionHandler */
foreach ($this->exceptionHandlers as $key => $handler) {
foreach (array_reverse($this->exceptionHandlers) as $key => $handler) {
if (\is_object($handler) === false) {
if (is_object($handler) === false) {
$handler = new $handler();
}
$this->fireEvents(EventHandler::EVENT_RENDER_EXCEPTION, [
'exception' => $e,
'exceptionHandler' => $handler,
'exception' => $e,
'exceptionHandler' => $handler,
'exceptionHandlers' => $this->exceptionHandlers,
]);
$this->debug('Processing exception-handler "%s"', \get_class($handler));
$this->debug('Processing exception-handler "%s"', get_class($handler));
if (($handler instanceof IExceptionHandler) === false) {
throw new HttpException('Exception handler must implement the IExceptionHandler interface.', 500);
}
try {
$this->debug('Start rendering exception handler');
$handler->handleError($this->request, $e);
$this->debug('Finished rendering exception-handler');
if (isset($this->loadedExceptionHandlers[$key]) === false && $this->request->hasPendingRewrite() === true) {
$this->loadedExceptionHandlers[$key] = $handler;
$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(),
]);
if ($this->request->getRewriteRoute() !== null) {
$this->processedRoutes[] = $this->request->getRewriteRoute();
}
return $this->routeRequest();
}
} catch (\Exception $e) {
} catch (Exception $e) {
}
@@ -547,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. */
@@ -558,17 +602,17 @@ class Router
}
/* Direct match to controller */
if ($route instanceof IControllerRoute && strtolower($route->getController()) === strtolower($name)) {
if ($route instanceof IControllerRoute && strtoupper($route->getController()) === strtoupper($name)) {
$this->debug('Found route "%s" by controller "%s"', $route->getUrl(), $name);
return $route;
}
/* 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;
@@ -577,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 && strpos($name, '@') !== false && strpos($callback, '@') !== false && \is_callable($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)) {
@@ -617,32 +661,23 @@ class Router
* @param array|null $getParams
* @return Url
* @throws InvalidArgumentException
* @throws \Pecee\Http\Exceptions\MalformedUrlException
*/
public function getUrl(?string $name = null, $parameters = null, ?array $getParams = null): Url
{
$this->debug('Finding url', \func_get_args());
$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('/');
}
/* Only merge $_GET when all parameters are null */
if ($name === null && $parameters === null && $getParams === null) {
$getParams = $_GET;
} else {
$getParams = (array)$getParams;
}
$getParams = ($name === null && $parameters === null && $getParams === null) ? $_GET : (array)$getParams;
/* Return current route if no options has been specified */
if ($name === null && $parameters === null) {
@@ -655,45 +690,35 @@ class Router
/* If nothing is defined and a route is loaded we use that */
if ($name === null && $loadedRoute !== null) {
return $this->request
->getUrlCopy()
->setPath($loadedRoute->findUrl($loadedRoute->getMethod(), $parameters, $name))
->setParams($getParams);
return $this->request->getUrlCopy()->parse($loadedRoute->findUrl($loadedRoute->getMethod(), $parameters, $name))->setParams($getParams);
}
/* We try to find a match on the given name */
$route = $this->findRoute($name);
if ($name !== null) {
/* We try to find a match on the given name */
$route = $this->findRoute($name);
if ($route !== null) {
return $this->request
->getUrlCopy()
->setPath($route->findUrl($route->getMethod(), $parameters, $name))
->setParams($getParams);
if ($route !== null) {
return $this->request->getUrlCopy()->parse($route->findUrl($route->getMethod(), $parameters, $name))->setParams($getParams);
}
}
/* Using @ is most definitely a controller@method or alias@method */
if (\is_string($name) === true && strpos($name, '@') !== false) {
if (is_string($name) === true && strpos($name, '@') !== false) {
[$controller, $method] = explode('@', $name);
/* 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) {
return $this->request
->getUrlCopy()
->setPath($route->findUrl($method, $parameters, $name))
->setParams($getParams);
if ($processedRoute->hasName($controller) === true) {
return $this->request->getUrlCopy()->parse($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)) {
return $this->request
->getUrlCopy()
->setPath($route->findUrl($method, $parameters, $name))
->setParams($getParams);
if ($processedRoute instanceof IControllerRoute && strtolower($processedRoute->getController()) === strtolower($controller)) {
return $this->request->getUrlCopy()->parse($processedRoute->findUrl($method, $parameters, $name))->setParams($getParams);
}
}
@@ -703,10 +728,7 @@ class Router
$url = trim(implode('/', array_merge((array)$name, (array)$parameters)), '/');
$url = (($url === '') ? '/' : '/' . $url . '/');
return $this->request
->getUrlCopy()
->setPath($url)
->setParams($getParams);
return $this->request->getUrlCopy()->parse($url)->setParams($getParams);
}
/**
@@ -798,32 +820,26 @@ class Router
* Set csrf verifier class
*
* @param BaseCsrfVerifier $csrfVerifier
* @return static
*/
public function setCsrfVerifier(BaseCsrfVerifier $csrfVerifier): self
public function setCsrfVerifier(BaseCsrfVerifier $csrfVerifier): void
{
$this->csrfVerifier = $csrfVerifier;
return $this;
}
/**
* Set class loader
*
* @param IClassLoader $loader
* @return static
*/
public function setClassLoader(IClassLoader $loader)
public function setClassLoader(IClassLoader $loader): void
{
$this->classLoader = $loader;
return $this;
}
/**
* Get class loader
*
* @return ClassLoader
* @return IClassLoader
*/
public function getClassLoader(): IClassLoader
{
@@ -834,13 +850,10 @@ class Router
* Register event handler
*
* @param IEventHandler $handler
* @return static
*/
public function addEventHandler(IEventHandler $handler): self
public function addEventHandler(IEventHandler $handler): void
{
$this->eventHandlers[] = $handler;
return $this;
}
/**
@@ -859,9 +872,9 @@ class Router
* @param string $name
* @param array $arguments
*/
protected function fireEvents($name, array $arguments = []): void
protected function fireEvents(string $name, array $arguments = []): void
{
if (\count($this->eventHandlers) === 0) {
if (count($this->eventHandlers) === 0) {
return;
}
@@ -885,8 +898,8 @@ class Router
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
$this->debugList[] = [
'message' => vsprintf($message, $args),
'time' => number_format(microtime(true) - $this->debugStartTime, 10),
'trace' => end($trace),
'time' => number_format(microtime(true) - $this->debugStartTime, 10),
'trace' => end($trace),
];
}
@@ -913,4 +926,36 @@ 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.
* When disabled the router will stop rendering at the first route that matches.
*
* @param bool $bool
* @return $this
*/
public function setRenderMultipleRoutes(bool $bool): self
{
$this->renderMultipleRoutes = $bool;
return $this;
}
public function addExceptionHandler(IExceptionHandler $handler): self
{
$this->exceptionHandlers[] = $handler;
return $this;
}
}
+103 -124
View File
@@ -4,15 +4,15 @@
* Router helper class
* ---------------------------
*
* This class is added so calls can be made statically like Router::get() making the code look pretty.
* It also adds some extra functionality like default-namespace.
* This class is added so calls can be made statically like SimpleRouter::get() making the code look pretty.
* It also adds some extra functionality like default-namespace etc.
*/
namespace Pecee\SimpleRouter;
use DI\Container;
use Closure;
use Exception;
use Pecee\Exceptions\InvalidArgumentException;
use Pecee\Http\Exceptions\MalformedUrlException;
use Pecee\Http\Middleware\BaseCsrfVerifier;
use Pecee\Http\Request;
use Pecee\Http\Response;
@@ -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
@@ -56,10 +57,15 @@ class SimpleRouter
* @throws \Pecee\SimpleRouter\Exceptions\NotFoundHttpException
* @throws \Pecee\Http\Middleware\Exceptions\TokenMismatchException
* @throws HttpException
* @throws \Exception
* @throws Exception
*/
public static function start(): void
{
// Set default namespaces
foreach (static::router()->getRoutes() as $route) {
static::addDefaultNamespace($route);
}
echo static::router()->start();
}
@@ -74,22 +80,20 @@ class SimpleRouter
try {
ob_start();
static::router()->setDebugEnabled(true);
static::start();
$routerOutput = ob_get_contents();
ob_end_clean();
} catch (\Exception $e) {
static::router()->setDebugEnabled(true)->start();
$routerOutput = ob_get_clean();
} catch (Exception $e) {
}
// Try to parse library version
$composerFile = \dirname(__DIR__, 3) . '/composer.lock';
$composerFile = dirname(__DIR__, 3) . '/composer.lock';
$version = false;
if (is_file($composerFile) === true) {
$composerInfo = json_decode(file_get_contents($composerFile), true);
if (isset($composerInfo['packages']) === true && \is_array($composerInfo['packages']) === true) {
if (isset($composerInfo['packages']) === true && is_array($composerInfo['packages']) === true) {
foreach ($composerInfo['packages'] as $package) {
if (isset($package['name']) === true && strtolower($package['name']) === 'pecee/simple-router') {
$version = $package['version'];
@@ -160,99 +164,110 @@ class SimpleRouter
static::router()->addBootManager($bootManager);
}
/**
* Redirect to when route matches.
*
* @param string $where
* @param string $to
* @param int $httpCode
* @return IRoute
*/
public static function redirect(string $where, string $to, int $httpCode = 301): IRoute
{
return static::get($where, static function () use ($to, $httpCode): void {
static::response()->redirect($to, $httpCode);
});
}
/**
* Route the given url to your callback on GET request method.
*
* @param string $url
* @param string|\Closure $callback
* @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
{
return static::match(['get'], $url, $callback, $settings);
return static::match([Request::REQUEST_TYPE_GET], $url, $callback, $settings);
}
/**
* Route the given url to your callback on POST request method.
*
* @param string $url
* @param string|\Closure $callback
* @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
{
return static::match(['post'], $url, $callback, $settings);
return static::match([Request::REQUEST_TYPE_POST], $url, $callback, $settings);
}
/**
* Route the given url to your callback on PUT request method.
*
* @param string $url
* @param string|\Closure $callback
* @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
{
return static::match(['put'], $url, $callback, $settings);
return static::match([Request::REQUEST_TYPE_PUT], $url, $callback, $settings);
}
/**
* Route the given url to your callback on PATCH request method.
*
* @param string $url
* @param string|\Closure $callback
* @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
{
return static::match(['patch'], $url, $callback, $settings);
return static::match([Request::REQUEST_TYPE_PATCH], $url, $callback, $settings);
}
/**
* Route the given url to your callback on OPTIONS request method.
*
* @param string $url
* @param string|\Closure $callback
* @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
{
return static::match(['options'], $url, $callback, $settings);
return static::match([Request::REQUEST_TYPE_OPTIONS], $url, $callback, $settings);
}
/**
* Route the given url to your callback on DELETE request method.
*
* @param string $url
* @param string|\Closure $callback
* @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
{
return static::match(['delete'], $url, $callback, $settings);
return static::match([Request::REQUEST_TYPE_DELETE], $url, $callback, $settings);
}
/**
* Groups allows for encapsulating routes with special settings.
*
* @param array $settings
* @param \Closure $callback
* @return RouteGroup
* @param Closure $callback
* @return RouteGroup|IGroupRoute
* @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->setCallback($callback);
$group->setSettings($settings);
@@ -267,17 +282,13 @@ class SimpleRouter
* parameters and which are only rendered when the url matches.
*
* @param string $url
* @param \Closure $callback
* @param Closure $callback
* @param array $settings
* @return RoutePartialGroup
* @return RoutePartialGroup|IPartialGroupRoute
* @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;
$group = new RoutePartialGroup();
@@ -293,14 +304,14 @@ class SimpleRouter
* Alias for the form method
*
* @param string $url
* @param callable $callback
* @param string|array|Closure $callback
* @param array|null $settings
* @return RouteUrl|IRoute
* @see SimpleRouter::form
* @return RouteUrl
*/
public static function basic(string $url, $callback, array $settings = null): IRoute
{
return static::match(['get', 'post'], $url, $callback, $settings);
return static::form($url, $callback, $settings);
}
/**
@@ -308,14 +319,17 @@ class SimpleRouter
* Route the given url to your callback on POST and GET request method.
*
* @param string $url
* @param string|\Closure $callback
* @param string|array|Closure $callback
* @param array|null $settings
* @return RouteUrl|IRoute
* @see SimpleRouter::form
* @return RouteUrl
*/
public static function form(string $url, $callback, array $settings = null): IRoute
{
return static::match(['get', 'post'], $url, $callback, $settings);
return static::match([
Request::REQUEST_TYPE_GET,
Request::REQUEST_TYPE_POST,
], $url, $callback, $settings);
}
/**
@@ -323,45 +337,39 @@ class SimpleRouter
*
* @param array $requestMethods
* @param string $url
* @param string|\Closure $callback
* @param string|array|Closure $callback
* @param array|null $settings
* @return RouteUrl|IRoute
*/
public static function match(array $requestMethods, string $url, $callback, array $settings = null)
public static function match(array $requestMethods, string $url, $callback, array $settings = null): IRoute
{
$route = new RouteUrl($url, $callback);
$route->setRequestMethods($requestMethods);
$route = static::addDefaultNamespace($route);
if ($settings !== null) {
$route->setSettings($settings);
}
static::router()->addRoute($route);
return $route;
return static::router()->addRoute($route);
}
/**
* This type will route the given url to your callback and allow any type of request method
*
* @param string $url
* @param string|\Closure $callback
* @param string|array|Closure $callback
* @param array|null $settings
* @return RouteUrl|IRoute
*/
public static function all(string $url, $callback, array $settings = null)
public static function all(string $url, $callback, array $settings = null): IRoute
{
$route = new RouteUrl($url, $callback);
$route = static::addDefaultNamespace($route);
if ($settings !== null) {
$route->setSettings($settings);
}
static::router()->addRoute($route);
return $route;
return static::router()->addRoute($route);
}
/**
@@ -372,18 +380,15 @@ class SimpleRouter
* @param array|null $settings
* @return RouteController|IRoute
*/
public static function controller(string $url, $controller, array $settings = null)
public static function controller(string $url, string $controller, array $settings = null): IRoute
{
$route = new RouteController($url, $controller);
$route = static::addDefaultNamespace($route);
if ($settings !== null) {
$route->setSettings($settings);
}
static::router()->addRoute($route);
return $route;
return static::router()->addRoute($route);
}
/**
@@ -394,38 +399,28 @@ class SimpleRouter
* @param array|null $settings
* @return RouteResource|IRoute
*/
public static function resource(string $url, $controller, array $settings = null)
public static function resource(string $url, string $controller, array $settings = null): IRoute
{
$route = new RouteResource($url, $controller);
$route = static::addDefaultNamespace($route);
if ($settings !== null) {
$route->setSettings($settings);
}
static::router()->addRoute($route);
return $route;
return static::router()->addRoute($route);
}
/**
* Add exception callback handler.
*
* @param \Closure $callback
* @param Closure $callback
* @return CallbackExceptionHandler $callbackHandler
*/
public static function error(\Closure $callback): CallbackExceptionHandler
public static function error(Closure $callback): CallbackExceptionHandler
{
$routes = static::router()->getRoutes();
$callbackHandler = new CallbackExceptionHandler($callback);
$group = new RouteGroup();
$group->addExceptionHandler($callbackHandler);
array_unshift($routes, $group);
static::router()->setRoutes($routes);
static::router()->addExceptionHandler($callbackHandler);
return $callbackHandler;
}
@@ -447,26 +442,19 @@ class SimpleRouter
* @param array|null $getParams
* @return Url
*/
public static function getUrl(?string $name = null, $parameters = null, $getParams = null): Url
public static function getUrl(?string $name = null, $parameters = null, ?array $getParams = null): Url
{
try {
return static::router()->getUrl($name, $parameters, $getParams);
} catch (\Exception $e) {
try {
return new Url('/');
} catch (MalformedUrlException $e) {
}
} catch (Exception $e) {
return new Url('/');
}
// This will never happen...
return null;
}
/**
* Get the request
*
* @return \Pecee\Http\Request
* @return Request
*/
public static function request(): Request
{
@@ -504,46 +492,37 @@ 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
{
if (static::$defaultNamespace !== null) {
$callback = $route->getCallback();
/* Only add default namespace on relative callbacks */
if ($callback === null || (\is_string($callback) === true && $callback[0] !== '\\')) {
$namespace = static::$defaultNamespace;
$currentNamespace = $route->getNamespace();
if ($currentNamespace !== null) {
$namespace .= '\\' . $currentNamespace;
}
$route->setDefaultNamespace($namespace);
}
$route->setNamespace(static::$defaultNamespace);
}
return $route;
}
/**
* Enable or disable dependency injection
* Changes the rendering behavior of the router.
* When enabled the router will render all routes that matches.
* When disabled the router will stop rendering at the first route that matches.
*
* @param Container $container
* @return IClassLoader
* @param bool $bool
*/
public static function enableDependencyInjection(Container $container): IClassLoader
public static function enableMultiRouteRendering(bool $bool): void
{
return static::router()
->getClassLoader()
->useDependencyInjection(true)
->setContainer($container);
static::router()->setRenderMultipleRoutes($bool);
}
/**
* Set custom class-loader class used.
* @param IClassLoader $classLoader
*/
public static function setCustomClassLoader(IClassLoader $classLoader): void
{
static::router()->setClassLoader($classLoader);
}
/**
@@ -0,0 +1,49 @@
<?php
require_once 'Dummy/DummyMiddleware.php';
require_once 'Dummy/DummyController.php';
require_once 'Dummy/Handler/ExceptionHandler.php';
require_once 'Dummy/Managers/TestBootManager.php';
require_once 'Dummy/Managers/FindUrlBootManager.php';
class BootManagerTest extends \PHPUnit\Framework\TestCase
{
public function testBootManagerRoutes()
{
$result = false;
TestRouter::get('/', function () use (&$result) {
$result = true;
});
TestRouter::get('/about', 'DummyController@method2');
TestRouter::get('/contact', 'DummyController@method3');
// Add boot-manager
TestRouter::addBootManager(new TestBootManager([
'/con' => '/about',
'/contact' => '/',
]));
TestRouter::debug('/contact');
$this->assertTrue($result);
}
public function testFindUrlFromBootManager()
{
TestRouter::get('/', 'DummyController@method1');
TestRouter::get('/about', 'DummyController@method2')->name('about');
TestRouter::get('/contact', 'DummyController@method3')->name('contact');
$result = false;
// Add boot-manager
TestRouter::addBootManager(new FindUrlBootManager($result));
TestRouter::debug('/');
$this->assertTrue($result);
}
}
@@ -0,0 +1,30 @@
<?php
require_once 'Dummy/DummyMiddleware.php';
require_once 'Dummy/DummyController.php';
require_once 'Dummy/ClassLoader/CustomClassLoader.php';
class ClassLoaderTest extends \PHPUnit\Framework\TestCase
{
public function testCustomClassLoader()
{
$result = false;
TestRouter::setCustomClassLoader(new CustomClassLoader());
TestRouter::get('/', 'NonExistingClass@method3');
TestRouter::get('/test-closure', function($status) use(&$result) {
$result = $status;
});
$classLoaderClass = TestRouter::debugOutput('/', 'get', false);
TestRouter::debugOutput('/test-closure');
$this->assertEquals('method3', $classLoaderClass);
$this->assertTrue($result);
TestRouter::router()->reset();
}
}
@@ -0,0 +1,66 @@
<?php
require_once 'Dummy/CsrfVerifier/DummyCsrfVerifier.php';
require_once 'Dummy/Security/SilentTokenProvider.php';
class CsrfVerifierTest extends \PHPUnit\Framework\TestCase
{
public function testTokenPass()
{
global $_POST;
$tokenProvider = new SilentTokenProvider();
$_POST[DummyCsrfVerifier::POST_KEY] = $tokenProvider->getToken();
TestRouter::router()->reset();
$router = TestRouter::router();
$router->getRequest()->setMethod(\Pecee\Http\Request::REQUEST_TYPE_POST);
$router->getRequest()->setUrl(new \Pecee\Http\Url('/page'));
$csrf = new DummyCsrfVerifier();
$csrf->setTokenProvider($tokenProvider);
$csrf->handle($router->getRequest());
// If handle doesn't throw exception, the test has passed
$this->assertTrue(true);
}
public function testTokenFail()
{
$this->expectException(\Pecee\Http\Middleware\Exceptions\TokenMismatchException::class);
global $_POST;
$tokenProvider = new SilentTokenProvider();
$router = TestRouter::router();
$router->getRequest()->setMethod(\Pecee\Http\Request::REQUEST_TYPE_POST);
$router->getRequest()->setUrl(new \Pecee\Http\Url('/page'));
$csrf = new DummyCsrfVerifier();
$csrf->setTokenProvider($tokenProvider);
$csrf->handle($router->getRequest());
}
public function testExcludeInclude()
{
$router = TestRouter::router();
$csrf = new DummyCsrfVerifier();
$request = $router->getRequest();
$request->setUrl(new \Pecee\Http\Url('/exclude-page'));
$this->assertTrue($csrf->testSkip($router->getRequest()));
$request->setUrl(new \Pecee\Http\Url('/exclude-all/page'));
$this->assertTrue($csrf->testSkip($router->getRequest()));
$request->setUrl(new \Pecee\Http\Url('/exclude-all/include-page'));
$this->assertFalse($csrf->testSkip($router->getRequest()));
$request->setUrl(new \Pecee\Http\Url('/include-page'));
$this->assertFalse($csrf->testSkip($router->getRequest()));
}
}
@@ -0,0 +1,71 @@
<?php
require_once 'Dummy/DummyController.php';
require_once 'Dummy/Middleware/IpRestrictMiddleware.php';
class CustomMiddlewareTest extends \PHPUnit\Framework\TestCase
{
public function testIpBlock() {
$this->expectException(\Pecee\SimpleRouter\Exceptions\HttpException::class);
global $_SERVER;
// Test exact ip
$_SERVER['remote-addr'] = '5.5.5.5';
TestRouter::group(['middleware' => IpRestrictMiddleware::class], function() {
TestRouter::get('/fail', 'DummyController@method1');
});
TestRouter::debug('/fail');
// Test ip-range
$_SERVER['remote-addr'] = '8.8.4.4';
TestRouter::router()->reset();
TestRouter::group(['middleware' => IpRestrictMiddleware::class], function() {
TestRouter::get('/fail', 'DummyController@method1');
});
TestRouter::debug('/fail');
}
public function testIpSuccess() {
global $_SERVER;
// Test ip that is not blocked
$_SERVER['remote-addr'] = '6.6.6.6';
TestRouter::router()->reset();
TestRouter::group(['middleware' => IpRestrictMiddleware::class], function() {
TestRouter::get('/success', 'DummyController@method1');
});
TestRouter::debug('/success');
// Test ip in whitelist
$_SERVER['remote-addr'] = '8.8.2.2';
TestRouter::router()->reset();
TestRouter::group(['middleware' => IpRestrictMiddleware::class], function() {
TestRouter::get('/success', 'DummyController@method1');
});
TestRouter::debug('/success');
$this->assertTrue(true);
}
}
@@ -1,53 +0,0 @@
<?php
require_once 'Dummy/DummyMiddleware.php';
class DependencyInjectionTest extends \PHPUnit\Framework\TestCase
{
public function testDependencyInjectionDevelopment()
{
$builder = new \DI\ContainerBuilder();
$container = $builder
->useAutowiring(true)
->ignorePhpDocErrors(true)
->build();
TestRouter::enableDependencyInjection($container);
$className = null;
TestRouter::get('/', function (DummyMiddleware $url) use (&$className) {
$className = \get_class($url);
});
TestRouter::debug('/');
$this->assertEquals(DummyMiddleware::class, $className);
}
public function testDependencyInjectionProduction()
{
$cacheDir = dirname(__DIR__, 2) . '/tmp';
$builder = new \DI\ContainerBuilder();
$builder
->enableCompilation($cacheDir)
->writeProxiesToFile(true, $cacheDir . '/proxies')
->ignorePhpDocErrors(true)
->useAutowiring(true);
$container = $builder->build();
TestRouter::enableDependencyInjection($container);
$className = null;
TestRouter::get('/', function (DummyMiddleware $url) use (&$className) {
$className = \get_class($url);
});
TestRouter::debug('/');
$this->assertEquals(DummyMiddleware::class, $className);
}
}
@@ -0,0 +1,26 @@
<?php
class CustomClassLoader implements \Pecee\SimpleRouter\ClassLoader\IClassLoader
{
public function loadClass(string $class)
{
return new DummyController();
}
/**
* Called when loading class method
* @param object $class
* @param string $method
* @param array $parameters
* @return object
*/
public function loadClassMethod($class, string $method, array $parameters)
{
return call_user_func_array([$class, $method], [true]);
}
public function loadClosure(callable $closure, array $parameters)
{
return call_user_func_array($closure, [true]);
}
}
@@ -0,0 +1,18 @@
<?php
class DummyCsrfVerifier extends \Pecee\Http\Middleware\BaseCsrfVerifier {
protected array $except = [
'/exclude-page',
'/exclude-all/*',
];
protected array $include = [
'/exclude-all/include-page',
];
public function testSkip(\Pecee\Http\Request $request) {
return $this->skip($request);
}
}
@@ -2,6 +2,12 @@
class DummyController
{
public function index()
{
}
public function method1()
{
@@ -10,6 +16,11 @@ class DummyController
public function method2()
{
}
public function method3()
{
return 'method3';
}
public function param($params = null)
@@ -0,0 +1,26 @@
<?php
class FindUrlBootManager implements \Pecee\SimpleRouter\IRouterBootManager
{
protected $result;
public function __construct(&$result)
{
$this->result = &$result;
}
/**
* Called when router loads it's routes
*
* @param \Pecee\SimpleRouter\Router $router
* @param \Pecee\Http\Request $request
*/
public function boot(\Pecee\SimpleRouter\Router $router, \Pecee\Http\Request $request): void
{
$contact = $router->findRoute('contact');
if($contact !== null) {
$this->result = true;
}
}
}
@@ -3,13 +3,11 @@
class TestBootManager implements \Pecee\SimpleRouter\IRouterBootManager
{
protected $routes;
protected $aliasUrl;
protected $rewrite;
public function __construct(array $routes, string $aliasUrl)
public function __construct(array $rewrite)
{
$this->routes = $routes;
$this->aliasUrl = $aliasUrl;
$this->rewrite = $rewrite;
}
/**
@@ -20,11 +18,11 @@ class TestBootManager implements \Pecee\SimpleRouter\IRouterBootManager
*/
public function boot(\Pecee\SimpleRouter\Router $router, \Pecee\Http\Request $request): void
{
foreach ($this->routes as $url) {
foreach ($this->rewrite as $url => $rewrite) {
// If the current url matches the rewrite url, we use our custom route
if ($request->getUrl()->contains($url) === true) {
$request->setRewriteUrl($this->aliasUrl);
$request->setRewriteUrl($rewrite);
}
}
@@ -0,0 +1,14 @@
<?php
class IpRestrictMiddleware extends \Pecee\Http\Middleware\IpRestrictAccess {
protected array $ipBlacklist = [
'5.5.5.5',
'8.8.*',
];
protected array $ipWhitelist = [
'8.8.2.2',
];
}
@@ -0,0 +1,11 @@
<?php
namespace MyNamespace;
class NSController {
public function method()
{
return true;
}
}
@@ -4,43 +4,36 @@ class ResourceController implements \Pecee\Controllers\IResourceController
public function index() : ?string
{
echo 'index';
return null;
return 'index';
}
public function show($id) : ?string
{
echo 'show ' . $id;
return null;
return 'show ' . $id;
}
public function store() : ?string
{
echo 'store';
return null;
return 'store';
}
public function create() : ?string
{
echo 'create';
return null;
return 'create';
}
public function edit($id) : ?string
{
echo 'edit ' . $id;
return null;
return 'edit ' . $id;
}
public function update($id) : ?string
{
echo 'update ' . $id;
return null;
return 'update ' . $id;
}
public function destroy($id) : ?string
{
echo 'destroy ' . $id;
return null;
return 'destroy ' . $id;
}
}
@@ -0,0 +1,11 @@
<?php
use Pecee\Http\Request;
class DummyLoadableRoute extends Pecee\SimpleRouter\Route\LoadableRoute {
public function matchRoute(string $url, Request $request): bool
{
return false;
}
}
+75 -5
View File
@@ -6,8 +6,8 @@ require_once 'Dummy/Handler/ExceptionHandler.php';
require_once 'Dummy/Security/SilentTokenProvider.php';
require_once 'Dummy/Managers/TestBootManager.php';
use \Pecee\SimpleRouter\Handlers\EventHandler;
use \Pecee\SimpleRouter\Event\EventArgument;
use Pecee\SimpleRouter\Event\EventArgument;
use Pecee\SimpleRouter\Handlers\EventHandler;
class EventHandlerTest extends \PHPUnit\Framework\TestCase
{
@@ -50,8 +50,8 @@ class EventHandlerTest extends \PHPUnit\Framework\TestCase
// Add boot-manager
TestRouter::addBootManager(new TestBootManager([
'/test',
], '/'));
'/test' => '/',
]));
// Start router
TestRouter::debug('/non-existing');
@@ -61,7 +61,6 @@ class EventHandlerTest extends \PHPUnit\Framework\TestCase
public function testAllEvent()
{
$status = false;
$eventHandler = new EventHandler();
@@ -78,4 +77,75 @@ class EventHandlerTest extends \PHPUnit\Framework\TestCase
$this->assertEquals(true, $status);
}
public function testPrefixEvent()
{
$eventHandler = new EventHandler();
$eventHandler->register(EventHandler::EVENT_ADD_ROUTE, function (EventArgument $arg) use (&$status) {
if ($arg->route instanceof \Pecee\SimpleRouter\Route\LoadableRoute) {
$arg->route->prependUrl('/local-path');
}
});
TestRouter::addEventHandler($eventHandler);
$status = false;
TestRouter::get('/', function () use (&$status) {
$status = true;
});
TestRouter::debug('/local-path');
$this->assertTrue($status);
}
public function testCustomBasePath() {
$basePath = '/basepath/';
$eventHandler = new EventHandler();
$eventHandler->register(EventHandler::EVENT_ADD_ROUTE, function(EventArgument $data) use($basePath) {
// Skip routes added by group
if($data->isSubRoute === false) {
switch (true) {
case $data->route instanceof \Pecee\SimpleRouter\Route\ILoadableRoute:
$data->route->prependUrl($basePath);
break;
case $data->route instanceof \Pecee\SimpleRouter\Route\IGroupRoute:
$data->route->prependPrefix($basePath);
break;
}
}
});
$results = [];
TestRouter::addEventHandler($eventHandler);
TestRouter::get('/about', function() use(&$results) {
$results[] = 'about';
});
TestRouter::group(['prefix' => '/admin'], function() use(&$results) {
TestRouter::get('/', function() use(&$results) {
$results[] = 'admin';
});
});
TestRouter::router()->setRenderMultipleRoutes(false);
TestRouter::debugNoReset('/basepath/about');
TestRouter::debugNoReset('/basepath/admin');
$this->assertEquals(['about', 'admin'], $results);
}
}
+261 -9
View File
@@ -1,35 +1,287 @@
<?php
use Pecee\Http\Input\InputFile;
require_once 'Dummy/DummyMiddleware.php';
require_once 'Dummy/DummyController.php';
require_once 'Dummy/Handler/ExceptionHandler.php';
class InputHandlerTest extends \PHPUnit\Framework\TestCase
{
protected $names = [
'Lester',
'Michael',
'Franklin',
'Trevor',
];
public function testGet()
{
$this->assertEquals(true, true);
}
protected $brands = [
'Samsung',
'Apple',
'HP',
'Canon',
];
protected $sodas = [
0 => 'Pepsi',
1 => 'Coca Cola',
2 => 'Harboe',
3 => 'Mountain Dew',
];
protected $day = 'monday';
public function testPost()
{
$this->assertEquals(true, true);
global $_POST;
$_POST = [
'names' => $this->names,
'day' => $this->day,
'sodas' => $this->sodas,
];
$router = TestRouter::router();
$router->reset();
$router->getRequest()->setMethod('post');
$handler = TestRouter::request()->getInputHandler();
$this->assertEquals($this->names, $handler->value('names'));
$this->assertEquals($this->names, $handler->all(['names'])['names']);
$this->assertEquals($this->day, $handler->value('day'));
$this->assertInstanceOf(\Pecee\Http\Input\InputItem::class, $handler->find('day'));
$this->assertInstanceOf(\Pecee\Http\Input\InputItem::class, $handler->post('day'));
$this->assertInstanceOf(\Pecee\Http\Input\InputItem::class, $handler->find('day', 'post'));
// Check non-existing and wrong request-type
$this->assertCount(1, $handler->all(['non-existing']));
$this->assertEmpty($handler->all(['non-existing'])['non-existing']);
$this->assertNull($handler->value('non-existing'));
$this->assertNull($handler->find('non-existing'));
$this->assertNull($handler->value('names', null, 'get'));
$this->assertNull($handler->find('names', 'get'));
$this->assertEquals($this->sodas, $handler->value('sodas'));
$objects = $handler->find('names');
$this->assertInstanceOf(\Pecee\Http\Input\InputItem::class, $objects);
$this->assertCount(4, $objects);
/* @var $object \Pecee\Http\Input\InputItem */
foreach($objects as $i => $object) {
$this->assertInstanceOf(\Pecee\Http\Input\InputItem::class, $object);
$this->assertEquals($this->names[$i], $object->getValue());
}
// Reset
$_POST = [];
}
public function testGet()
{
global $_GET;
$_GET = [
'names' => $this->names,
'day' => $this->day,
];
$router = TestRouter::router();
$router->reset();
$router->getRequest()->setMethod('get');
$handler = TestRouter::request()->getInputHandler();
$this->assertEquals($this->names, $handler->value('names'));
$this->assertEquals($this->names, $handler->all(['names'])['names']);
$this->assertEquals($this->day, $handler->value('day'));
$this->assertInstanceOf(\Pecee\Http\Input\InputItem::class, $handler->find('day'));
$this->assertInstanceOf(\Pecee\Http\Input\InputItem::class, $handler->get('day'));
// Check non-existing and wrong request-type
$this->assertCount(1, $handler->all(['non-existing']));
$this->assertEmpty($handler->all(['non-existing'])['non-existing']);
$this->assertNull($handler->value('non-existing'));
$this->assertNull($handler->find('non-existing'));
$this->assertNull($handler->value('names', null, 'post'));
$this->assertNull($handler->find('names', 'post'));
$objects = $handler->find('names');
$this->assertInstanceOf(\Pecee\Http\Input\InputItem::class, $objects);
$this->assertCount(4, $objects);
/* @var $object \Pecee\Http\Input\InputItem */
foreach($objects as $i => $object) {
$this->assertInstanceOf(\Pecee\Http\Input\InputItem::class, $object);
$this->assertEquals($this->names[$i], $object->getValue());
}
// Reset
$_GET = [];
}
public function testFindInput() {
global $_POST;
$_POST['hello'] = 'motto';
$router = TestRouter::router();
$router->reset();
$router->getRequest()->setMethod('post');
$inputHandler = TestRouter::request()->getInputHandler();
$value = $inputHandler->value('hello', null, \Pecee\Http\Request::$requestTypesPost);
$this->assertEquals($_POST['hello'], $value);
}
public function testFile()
{
$this->assertEquals(true, true);
global $_FILES;
$testFile = $this->generateFile();
$_FILES = [
'test_input' => $testFile,
];
$router = TestRouter::router();
$router->reset();
$router->getRequest()->setMethod('post');
$inputHandler = TestRouter::request()->getInputHandler();
$testFileContent = md5(uniqid('test', false));
$file = $inputHandler->file('test_input');
$this->assertInstanceOf(InputFile::class, $file);
$this->assertEquals($testFile['name'], $file->getFilename());
$this->assertEquals($testFile['type'], $file->getType());
$this->assertEquals($testFile['tmp_name'], $file->getTmpName());
$this->assertEquals($testFile['error'], $file->getError());
$this->assertEquals($testFile['size'], $file->getSize());
$this->assertEquals(pathinfo($testFile['name'], PATHINFO_EXTENSION), $file->getExtension());
file_put_contents($testFile['tmp_name'], $testFileContent);
$this->assertEquals($testFileContent, $file->getContents());
// Cleanup
unlink($testFile['tmp_name']);
}
public function testFiles()
public function testFilesArray()
{
$this->assertEquals(true, true);
global $_FILES;
$testFiles = [
$file = $this->generateFile(),
$file = $this->generateFile(),
$file = $this->generateFile(),
$file = $this->generateFile(),
$file = $this->generateFile(),
];
$_FILES = [
'my_files' => $testFiles,
];
$router = TestRouter::router();
$router->reset();
$router->getRequest()->setMethod('post');
$inputHandler = TestRouter::request()->getInputHandler();
$files = $inputHandler->file('my_files');
$this->assertCount(5, $files);
/* @var $file InputFile */
foreach ($files as $key => $file) {
$testFileContent = md5(uniqid('test', false));
$this->assertInstanceOf(InputFile::class, $file);
$this->assertEquals($testFiles[$key]['name'], $file->getFilename());
$this->assertEquals($testFiles[$key]['type'], $file->getType());
$this->assertEquals($testFiles[$key]['tmp_name'], $file->getTmpName());
$this->assertEquals($testFiles[$key]['error'], $file->getError());
$this->assertEquals($testFiles[$key]['size'], $file->getSize());
$this->assertEquals(pathinfo($testFiles[$key]['name'], PATHINFO_EXTENSION), $file->getExtension());
file_put_contents($testFiles[$key]['tmp_name'], $testFileContent);
$this->assertEquals($testFileContent, $file->getContents());
// Cleanup
unlink($testFiles[$key]['tmp_name']);
}
}
public function testAll()
{
$this->assertEquals(true, true);
global $_POST;
global $_GET;
$_POST = [
'names' => $this->names,
'is_sad' => true,
];
$_GET = [
'brands' => $this->brands,
'is_happy' => true,
];
$router = TestRouter::router();
$router->reset();
$router->getRequest()->setMethod('post');
$handler = TestRouter::request()->getInputHandler();
// GET
$brandsFound = $handler->all(['brands', 'nothing']);
$this->assertArrayHasKey('brands', $brandsFound);
$this->assertArrayHasKey('nothing', $brandsFound);
$this->assertEquals($this->brands, $brandsFound['brands']);
$this->assertNull($brandsFound['nothing']);
// POST
$namesFound = $handler->all(['names', 'nothing']);
$this->assertArrayHasKey('names', $namesFound);
$this->assertArrayHasKey('nothing', $namesFound);
$this->assertEquals($this->names, $namesFound['names']);
$this->assertNull($namesFound['nothing']);
// DEFAULT VALUE
$nonExisting = $handler->all([
'non-existing'
]);
$this->assertArrayHasKey('non-existing', $nonExisting);
$this->assertNull($nonExisting['non-existing']);
// Reset
$_GET = [];
$_POST = [];
}
protected function generateFile()
{
return [
'name' => uniqid('', false) . '.txt',
'type' => 'text/plain',
'tmp_name' => sys_get_temp_dir() . '/phpYfWUiw',
'error' => 0,
'size' => rand(3, 40),
];
}
protected function generateFileContent()
{
return md5(uniqid('', false));
}
}
@@ -0,0 +1,27 @@
<?php
require_once 'Dummy/Route/DummyLoadableRoute.php';
class LoadableRouteTest extends \PHPUnit\Framework\TestCase
{
public function testSetUrlUpdatesParameters()
{
$route = new DummyLoadableRoute();
$this->assertEmpty($route->getParameters());
$route->setUrl('/');
$this->assertEmpty($route->getParameters());
$expected = ['param' => null, 'optionalParam' => null];
$route->setUrl('/{param}/{optionalParam?}');
$this->assertEquals($expected, $route->getParameters());
$expected = ['otherParam' => null];
$route->setUrl('/{otherParam}');
$this->assertEquals($expected, $route->getParameters());
$expected = [];
$route->setUrl('/');
$this->assertEquals($expected, $route->getParameters());
}
}
+84
View File
@@ -0,0 +1,84 @@
<?php
use Pecee\Http\Input\InputFile;
require_once 'Dummy/DummyMiddleware.php';
require_once 'Dummy/DummyController.php';
require_once 'Dummy/Handler/ExceptionHandler.php';
class RequestTest extends \PHPUnit\Framework\TestCase
{
protected function processHeader($name, $value, callable $callback)
{
global $_SERVER;
$_SERVER[$name] = $value;
$router = TestRouter::router();
$router->reset();
$request = $router->getRequest();
$callback($request);
// Reset everything
$_SERVER[$name] = null;
$router->reset();
}
public function testContentTypeParse()
{
global $_SERVER;
// Test normal content-type
$contentType = 'application/x-www-form-urlencoded';
$this->processHeader('content_type', $contentType, function(\Pecee\Http\Request $request) use($contentType) {
$this->assertEquals($contentType, $request->getContentType());
});
// Test special content-type with encoding
$contentTypeWithEncoding = 'application/x-www-form-urlencoded; charset=UTF-8';
$this->processHeader('content_type', $contentTypeWithEncoding, function(\Pecee\Http\Request $request) use($contentType) {
$this->assertEquals($contentType, $request->getContentType());
});
}
public function testGetIp()
{
$ip = '1.1.1.1';
$this->processHeader('remote_addr', $ip, function(\Pecee\Http\Request $request) use($ip) {
$this->assertEquals($ip, $request->getIp());
});
$ip = '2.2.2.2';
$this->processHeader('http-cf-connecting-ip', $ip, function(\Pecee\Http\Request $request) use($ip) {
$this->assertEquals($ip, $request->getIp());
});
$ip = '3.3.3.3';
$this->processHeader('http-client-ip', $ip, function(\Pecee\Http\Request $request) use($ip) {
$this->assertEquals($ip, $request->getIp());
});
$ip = '4.4.4.4';
$this->processHeader('http-x-forwarded-for', $ip, function(\Pecee\Http\Request $request) use($ip) {
$this->assertEquals($ip, $request->getIp());
});
// Test safe
$ip = '5.5.5.5';
$this->processHeader('http-x-forwarded-for', $ip, function(\Pecee\Http\Request $request) {
$this->assertEquals(null, $request->getIp(true));
});
}
// TODO: implement more test-cases
}
@@ -19,10 +19,29 @@ class RouterCallbackExceptionHandlerTest extends \PHPUnit\Framework\TestCase
throw new ExceptionHandlerException();
});
TestRouter::debugNoReset('/404-url', 'get');
TestRouter::router()->reset();
TestRouter::debug('/404-url');
}
$this->assertTrue(true);
public function testExceptionHandlerCallback() {
TestRouter::group(['prefix' => null], function() {
TestRouter::get('/', function() {
return 'Hello world';
});
TestRouter::get('/not-found', 'DummyController@method1');
TestRouter::error(function(\Pecee\Http\Request $request, \Exception $exception) {
if($exception instanceof \Pecee\SimpleRouter\Exceptions\NotFoundHttpException && $exception->getCode() === 404) {
return $request->setRewriteCallback(static function() {
return 'success';
});
}
});
});
$result = TestRouter::debugOutput('/thisdoes-not/existssss', 'get');
$this->assertEquals('success', $result);
}
}
@@ -3,20 +3,20 @@
require_once 'Dummy/DummyMiddleware.php';
require_once 'Dummy/DummyController.php';
class GroupTest extends \PHPUnit\Framework\TestCase
class RouterGroupTest extends \PHPUnit\Framework\TestCase
{
public function testGroupLoad()
{
$result = false;
TestRouter::group(['prefix' => '/group'], function () use(&$result) {
TestRouter::group(['prefix' => '/group'], function () use (&$result) {
$result = true;
});
try {
TestRouter::debug('/', 'get');
} catch(\Exception $e) {
} catch (\Exception $e) {
}
$this->assertTrue($result);
@@ -81,4 +81,40 @@ class GroupTest extends \PHPUnit\Framework\TestCase
}
public function testNamespaceExtend()
{
TestRouter::group(['namespace' => '\My\Namespace'], function () use (&$result) {
TestRouter::group(['namespace' => 'Service'], function () use (&$result) {
TestRouter::get('/test', function () use (&$result) {
return \Pecee\SimpleRouter\SimpleRouter::router()->getRequest()->getLoadedRoute()->getNamespace();
});
});
});
$namespace = TestRouter::debugOutput('/test');
$this->assertEquals('\My\Namespace\Service', $namespace);
}
public function testNamespaceOverwrite()
{
TestRouter::group(['namespace' => '\My\Namespace'], function () use (&$result) {
TestRouter::group(['namespace' => '\Service'], function () use (&$result) {
TestRouter::get('/test', function () use (&$result) {
return \Pecee\SimpleRouter\SimpleRouter::router()->getRequest()->getLoadedRoute()->getNamespace();
});
});
});
$namespace = TestRouter::debugOutput('/test');
$this->assertEquals('\Service', $namespace);
}
}
@@ -25,4 +25,92 @@ class RouterPartialGroupTest extends \PHPUnit\Framework\TestCase
$this->assertEquals('param2', $result2);
}
/**
* Fixed issue with partial routes not loading child groups.
* Reported in issue: #456
*/
public function testPartialGroupWithGroup() {
$lang = null;
$route1 = '/lang/da/test/';
$route2 = '/lang/da/auth';
$route3 = '/lang/da/auth/test';
TestRouter::partialGroup(
'/lang/{test}/',
function ($lang = 'en') use($route1, $route2, $route3) {
TestRouter::get('/test/', function () use($route1) {
return $route1;
});
TestRouter::group(['prefix' => '/auth/'], function () use($route2, $route3) {
TestRouter::get('/', function() use($route2) {
return $route2;
});
TestRouter::get('/test', function () use($route3){
return $route3;
});
});
}
);
$test1 = TestRouter::debugOutput('/lang/da/test', 'get', false);
$test2 = TestRouter::debugOutput('/lang/da/auth', 'get', false);
$test3 = TestRouter::debugOutput('/lang/da/auth/test', 'get', false);
$this->assertEquals($test1, $route1);
$this->assertEquals($test2, $route2);
$this->assertEquals($test3, $route3);
}
public function testPhp8CallUserFunc() {
TestRouter::router()->reset();
$result = false;
$lang = 'de';
TestRouter::group(['prefix' => '/lang'], function() use(&$result) {
TestRouter::get('/{lang}', function ($lang) use(&$result) {
$result = $lang;
});
});
TestRouter::debug("/lang/$lang");
$this->assertEquals($lang, $result);
// Test partial group
$lang = 'de';
$userId = 22;
$result1 = false;
$result2 = false;
TestRouter::partialGroup(
'/lang/{lang}/',
function ($lang) use(&$result1, &$result2) {
$result1 = $lang;
TestRouter::get('/user/{userId}', function ($userId) use(&$result2) {
$result2 = $userId;
});
});
TestRouter::debug("/lang/$lang/user/$userId");
$this->assertEquals($lang, $result1);
$this->assertEquals($userId, $result2);
}
}
@@ -63,7 +63,22 @@ class RouterResourceTest extends \PHPUnit\Framework\TestCase
$response = TestRouter::debugOutput('/resource/38', 'get');
$this->assertEquals('show 38', $response);
}
public function testResourceUrls()
{
TestRouter::resource('/resource', 'ResourceController')->name('resource');
TestRouter::debugNoReset('/resource');
$this->assertEquals('/resource/3/create/', TestRouter::router()->getUrl('resource.create', ['id' => 3]));
$this->assertEquals('/resource/5/edit/', TestRouter::router()->getUrl('resource.edit', ['id' => 5]));
$this->assertEquals('/resource/6/', TestRouter::router()->getUrl('resource.update', ['id' => 6]));
$this->assertEquals('/resource/9/', TestRouter::router()->getUrl('resource.destroy', ['id' => 9]));
$this->assertEquals('/resource/12/', TestRouter::router()->getUrl('resource.delete', ['id' => 12]));
$this->assertEquals('/resource/', TestRouter::router()->getUrl('resource'));
TestRouter::router()->reset();
}
}
+33 -6
View File
@@ -6,7 +6,7 @@ require_once 'Dummy/Handler/ExceptionHandlerSecond.php';
require_once 'Dummy/Handler/ExceptionHandlerThird.php';
require_once 'Dummy/Middleware/RewriteMiddleware.php';
class RouteRewriteTest extends \PHPUnit\Framework\TestCase
class RouterRewriteTest extends \PHPUnit\Framework\TestCase
{
/**
@@ -33,9 +33,9 @@ class RouteRewriteTest 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,21 +43,48 @@ class RouteRewriteTest extends \PHPUnit\Framework\TestCase
});
try {
TestRouter::debug('/my-non-existing-path', 'get');
TestRouter::debug('/test/non-existing', 'get');
} catch (\ResponseException $e) {
}
$expectedStack = [
ExceptionHandlerFirst::class,
ExceptionHandlerSecond::class,
ExceptionHandlerThird::class,
ExceptionHandlerSecond::class,
ExceptionHandlerFirst::class,
];
$this->assertEquals($expectedStack, $stack);
}
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);
+178 -12
View File
@@ -2,14 +2,33 @@
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
{
/**
* Issue #421: Incorrectly optional character in route
*
* @throws Exception
*/
public function testOptionalCharacterRoute()
{
$result = false;
TestRouter::get('/api/v1/users/{userid}/projects/{id}/pages/{pageid?}', function () use (&$result) {
$result = true;
});
TestRouter::debug('/api/v1/users/1/projects/8399421535/pages/43/', 'get');
$this->assertTrue($result);
}
public function testMultiParam()
{
$result = false;
TestRouter::get('/test-{param1}-{param2}', function ($param1, $param2) use(&$result) {
TestRouter::get('/test-{param1}-{param2}', function ($param1, $param2) use (&$result) {
if ($param1 === 'param1' && $param2 === 'param2') {
$result = true;
@@ -83,24 +102,59 @@ class RouterRouteTest extends \PHPUnit\Framework\TestCase
public function testPathParamRegex()
{
TestRouter::get('/{lang}/productscategories/{name}', 'DummyController@param', ['where' => ['lang' => '[a-z]+', 'name' => '[A-Za-z0-9\-]+']]);
TestRouter::get('/{lang}/productscategories/{name}', 'DummyController@param', ['where' => ['lang' => '[a-z]+', 'name' => '[A-Za-z0-9-]+']]);
$response = TestRouter::debugOutput('/it/productscategories/system', 'get');
$this->assertEquals('it, system', $response);
}
public function testFixedDomain()
{
$result = false;
TestRouter::request()->setHost('admin.world.com');
TestRouter::group(['domain' => 'admin.world.com'], function () use (&$result) {
TestRouter::get('/test', function ($subdomain = null) use (&$result) {
$result = true;
});
});
TestRouter::debug('/test', 'get');
$this->assertTrue($result);
}
public function testFixedNotAllowedDomain()
{
$result = false;
TestRouter::request()->setHost('other.world.com');
TestRouter::group(['domain' => 'admin.world.com'], function () use (&$result) {
TestRouter::get('/', function ($subdomain = null) use (&$result) {
$result = true;
});
});
try {
TestRouter::debug('/', 'get');
} catch(\Exception $e) {
}
$this->assertFalse($result);
}
public function testDomainAllowedRoute()
{
$result = false;
TestRouter::request()->setHost('hello.world.com');
TestRouter::group(['domain' => '{subdomain}.world.com'], function () use(&$result) {
TestRouter::get('/test', function ($subdomain = null) use(&$result) {
TestRouter::group(['domain' => '{subdomain}.world.com'], function () use (&$result) {
TestRouter::get('/test', function ($subdomain = null) use (&$result) {
$result = ($subdomain === 'hello');
});
});
TestRouter::debug('/test', 'get');
$this->assertTrue($result);
@@ -113,31 +167,111 @@ class RouterRouteTest extends \PHPUnit\Framework\TestCase
$result = false;
TestRouter::group(['domain' => '{subdomain}.world.com'], function () use(&$result) {
TestRouter::get('/test', function ($subdomain = null) use(&$result) {
TestRouter::group(['domain' => '{subdomain}.world.com'], function () use (&$result) {
TestRouter::get('/test', function ($subdomain = null) use (&$result) {
$result = ($subdomain === 'hello');
});
});
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);
}
public function testRegEx()
{
TestRouter::get('/my/{path}', 'DummyController@method1')->where(['path' => '[a-zA-Z\-]+']);
TestRouter::get('/my/{path}', 'DummyController@method1')->where(['path' => '[a-zA-Z-]+']);
TestRouter::debug('/my/custom-path', 'get');
$this->assertTrue(true);
}
public function testParameterDefaultValue() {
public function testParametersWithDashes()
{
$defaultVariable = null;
TestRouter::get('/my/{path?}', function($path = 'working') use(&$defaultVariable) {
TestRouter::get('/my/{path}', function ($path = 'working') use (&$defaultVariable) {
$defaultVariable = $path;
});
TestRouter::debug('/my/hello-motto-man');
$this->assertEquals('hello-motto-man', $defaultVariable);
}
public function testParameterDefaultValue()
{
$defaultVariable = null;
TestRouter::get('/my/{path?}', function ($path = 'working') use (&$defaultVariable) {
$defaultVariable = $path;
});
@@ -149,7 +283,7 @@ class RouterRouteTest extends \PHPUnit\Framework\TestCase
public function testDefaultParameterRegex()
{
TestRouter::get('/my/{path}', 'DummyController@param', ['defaultParameterRegex' => '[\w\-]+']);
TestRouter::get('/my/{path}', 'DummyController@param', ['defaultParameterRegex' => '[\w-]+']);
$output = TestRouter::debugOutput('/my/custom-regex', 'get');
$this->assertEquals('custom-regex', $output);
@@ -157,7 +291,7 @@ class RouterRouteTest extends \PHPUnit\Framework\TestCase
public function testDefaultParameterRegexGroup()
{
TestRouter::group(['defaultParameterRegex' => '[\w\-]+'], function() {
TestRouter::group(['defaultParameterRegex' => '[\w-]+'], function () {
TestRouter::get('/my/{path}', 'DummyController@param');
});
@@ -166,4 +300,36 @@ class RouterRouteTest extends \PHPUnit\Framework\TestCase
$this->assertEquals('custom-regex', $output);
}
public function testClassHint()
{
TestRouter::get('/my/test/url', ['DummyController', 'method1']);
TestRouter::all('/my/test/url', ['DummyController', 'method1']);
TestRouter::match(['put', 'get', 'post'], '/my/test/url', ['DummyController', 'method1']);
TestRouter::debug('/my/test/url', 'get');
$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');
TestRouter::post('/recipe', 'DummyController@method2')->name('edit');
TestRouter::debugNoReset('/recipe', 'post');
TestRouter::debug('/recipe', 'get');
$this->assertTrue(true);
}
}
+222 -6
View File
@@ -11,7 +11,7 @@ class RouterUrlTest extends \PHPUnit\Framework\TestCase
{
TestRouter::get('/', 'DummyController@method1');
TestRouter::get('/page/{id?}', 'DummyController@method1');
TestRouter::get('/test-output', function() {
TestRouter::get('/test-output', function () {
return 'return value';
});
@@ -27,11 +27,28 @@ 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
TestRouter::get('/cursos/listado/{listado?}/{category?}', 'DummyController@method1', ['defaultParameterRegex' => '[\w\p{L}\s-]+']);
TestRouter::get('/test/{param}', 'DummyController@method1', ['defaultParameterRegex' => '[\w\p{L}\s-\í]+']);
TestRouter::get('/cursos/listado/{listado?}/{category?}', 'DummyController@method1', ['defaultParameterRegex' => '[\w\p{L}\s\-]+']);
TestRouter::get('/test/{param}', 'DummyController@method1', ['defaultParameterRegex' => '[\w\p{L}\s\-\í]+']);
TestRouter::debugNoReset('/cursos/listado/especialidad/cirugía local', 'get');
$this->assertEquals('/cursos/listado/{listado?}/{category?}/', TestRouter::router()->getRequest()->getLoadedRoute()->getUrl());
@@ -77,11 +94,14 @@ class RouterUrlTest extends \PHPUnit\Framework\TestCase
public function testSimilarUrls()
{
TestRouter::reset();
// Match normal route on alias
TestRouter::resource('/url11', 'DummyController@method1');
TestRouter::resource('/url1', 'DummyController@method1', ['as' => 'match']);
TestRouter::get('/url11', 'DummyController@method1');
TestRouter::get('/url22', 'DummyController@method2');
TestRouter::get('/url33', 'DummyController@method2')->name('match');
TestRouter::debugNoReset('/url1', 'get');
TestRouter::debugNoReset('/url33', 'get');
$this->assertEquals(TestRouter::getUrl('match'), TestRouter::getUrl());
@@ -166,7 +186,203 @@ class RouterUrlTest extends \PHPUnit\Framework\TestCase
// Should match /?jackdaniels=true&cola=yeah
$this->assertEquals('/?jackdaniels=true&cola=yeah', TestRouter::getUrl('home', null, ['jackdaniels' => 'true', 'cola' => 'yeah']));
TestRouter::reset();
}
public function testCustomRegex()
{
TestRouter::request()->setHost('google.com');
TestRouter::get('/admin/', function () {
return 'match';
})->setMatch('/^\/admin\/?(.*)/i');
$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()
{
TestRouter::router()->setRenderMultipleRoutes(false);
$result = false;
TestRouter::get('/', function () use (&$result) {
$result = true;
});
TestRouter::get('/', function () use (&$result) {
$result = false;
});
TestRouter::debug('/');
$this->assertTrue($result);
}
public function testRenderMultipleRoutesEnabled()
{
TestRouter::router()->setRenderMultipleRoutes(true);
$result = [];
TestRouter::get('/', function () use (&$result) {
$result[] = 'route1';
});
TestRouter::get('/', function () use (&$result) {
$result[] = 'route2';
});
TestRouter::debug('/');
$this->assertCount(2, $result);
}
public function testDefaultNamespace()
{
TestRouter::setDefaultNamespace('\\Default\\Namespace');
TestRouter::get('/', 'DummyController@method1', ['as' => 'home']);
TestRouter::group([
'namespace' => 'Appended\Namespace',
'prefix' => '/horses',
], function () {
TestRouter::get('/', 'DummyController@method1');
TestRouter::group([
'namespace' => '\\New\\Namespace',
'prefix' => '/race',
], function () {
TestRouter::get('/', 'DummyController@method1');
});
});
// Test appended namespace
$class = null;
try {
TestRouter::debugNoReset('/horses/');
} catch (\Pecee\SimpleRouter\Exceptions\ClassNotFoundHttpException $e) {
$class = $e->getClass();
}
$this->assertEquals('\\Default\\Namespace\\Appended\Namespace\\DummyController', $class);
// Test overwritten namespace
$class = null;
try {
TestRouter::debugNoReset('/horses/race');
} catch (\Pecee\SimpleRouter\Exceptions\ClassNotFoundHttpException $e) {
$class = $e->getClass();
}
$this->assertEquals('\\New\\Namespace\\DummyController', $class);
TestRouter::router()->reset();
}
public function testGroupPrefix()
{
$result = false;
TestRouter::group(['prefix' => '/lang/{lang}'], function () use (&$result) {
TestRouter::get('/test', function () use (&$result) {
$result = true;
});
});
TestRouter::debug('/lang/da/test');
$this->assertTrue($result);
// Test group prefix sub-route
$result = null;
$expectedResult = 28;
TestRouter::group(['prefix' => '/lang/{lang}'], function () use (&$result) {
TestRouter::get('/horse/{horseType}', function ($horseType) use (&$result) {
$result = false;
});
TestRouter::get('/user/{userId}', function ($userId) use (&$result) {
$result = $userId;
});
});
TestRouter::debug("/lang/da/user/$expectedResult");
$this->assertEquals($expectedResult, $result);
}
public function testPassParameter()
{
$result = false;
$expectedLanguage = 'da';
TestRouter::group(['prefix' => '/lang/{lang}'], function ($language) use (&$result) {
TestRouter::get('/test', function ($language) use (&$result) {
$result = $language;
});
});
TestRouter::debug("/lang/$expectedLanguage/test");
$this->assertEquals($expectedLanguage, $result);
}
public function testPassParameterDeep()
{
$result = false;
$expectedLanguage = 'da';
TestRouter::group(['prefix' => '/lang/{lang}'], function ($language) use (&$result) {
TestRouter::group(['prefix' => '/admin'], function ($language) use (&$result) {
TestRouter::get('/test', function ($language) use (&$result) {
$result = $language;
});
});
});
TestRouter::debug("/lang/$expectedLanguage/admin/test");
$this->assertEquals($expectedLanguage, $result);
}
+35 -9
View File
@@ -3,38 +3,64 @@
class TestRouter extends \Pecee\SimpleRouter\SimpleRouter
{
public static function debugNoReset($testUrl, $testMethod = 'get')
public function __construct()
{
static::request()->setHost('testhost.com');
}
public static function reset(): void
{
static::$router = null;
}
public static function debugNoReset(string $testUrl, string $testMethod = 'get'): void
{
$request = static::request();
$request->setUrl((new \Pecee\Http\Url($testUrl))->setHost('local.unitTest'));
$request->setUrl((new \Pecee\Http\Url($testUrl)));
$request->setMethod($testMethod);
static::start();
}
public static function debug($testUrl, $testMethod = 'get')
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;
}
static::router()->reset();
if ($reset === true) {
static::$defaultNamespace = null;
static::router()->reset();
}
}
public static function debugOutput($testUrl, $testMethod = 'get')
public static function debugOutput(string $testUrl, string $testMethod = 'get', bool $reset = true): string
{
$response = null;
// Route request
ob_start();
static::debug($testUrl, $testMethod);
$response = ob_get_contents();
ob_end_clean();
static::debug($testUrl, $testMethod, $reset);
$response = ob_get_clean();
// 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;