Compare commits

...

298 Commits

Author SHA1 Message Date
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ø 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ø 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ø 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ø 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ø 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
64 changed files with 2815 additions and 1093 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.1
- 7.4
phpunit-version:
- 7.5.20
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
+2 -2
View File
@@ -1,4 +1,4 @@
.idea
composer.lock
vendor/
tests/tmp/*
.idea/
.phpunit.result.cache
-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
+388 -184
View File
@@ -1,12 +1,20 @@
# simple-router
Simple, fast and yet powerful PHP router that is easy to get integrated and in any project.
Heavily inspired by the way Laravel handles routing, with both simplicity and expand-ability in mind.
Simple, fast and yet powerful PHP router that is easy to get integrated and in any project. Heavily inspired by the way Laravel handles routing, with both simplicity and expand-ability in mind.
With simple-router you can create a new project fast, without depending on a framework.
**It only takes a few lines of code to get started:**
```php
SimpleRouter::get('/', function() {
return 'Hello world';
});
```
### Support the project
If you like simple-router and wish to see the continued development and maintenance of the project,
please consider showing your support by buying me a coffee. Supporters will be listed under the credits section of this documentation.
If you like simple-router and wish to see the continued development and maintenance of the project, please consider showing your support by buying me a coffee. Supporters will be listed under the credits section of this documentation.
You can donate any amount of your choice by [clicking here](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=NNX4D2RUSALCN).
@@ -24,6 +32,7 @@ You can donate any amount of your choice by [clicking here](https://www.paypal.c
- [Helper functions](#helper-functions)
- [Routes](#routes)
- [Basic routing](#basic-routing)
- [Class hinting](#class-hinting)
- [Available methods](#available-methods)
- [Multiple HTTP-verbs](#multiple-http-verbs)
- [Route parameters](#route-parameters)
@@ -42,9 +51,6 @@ You can donate any amount of your choice by [clicking here](https://www.paypal.c
- [Partial groups](#partial-groups)
- [Form Method Spoofing](#form-method-spoofing)
- [Accessing The Current Route](#accessing-the-current-route)
- [Dependency injection](#dependency-injection)
- [Enabling dependency injection](#enabling-dependency-injection)
- [More reading](#more-reading)
- [Other examples](#other-examples)
- [CSRF-protection](#csrf-protection)
- [Adding CSRF-verifier](#adding-csrf-verifier)
@@ -76,14 +82,22 @@ You can donate any amount of your choice by [clicking here](https://www.paypal.c
- [Registering new event](#registering-new-event)
- [Custom EventHandlers](#custom-eventhandlers)
- [Advanced](#advanced)
- [Disable multiple route rendering](#disable-multiple-route-rendering)
- [Restrict access to IP](#restrict-access-to-ip)
- [Setting custom base path](#setting-custom-base-path)
- [Url rewriting](#url-rewriting)
- [Changing current route](#changing-current-route)
- [Bootmanager: loading routes dynamically](#bootmanager-loading-routes-dynamically)
- [Adding routes manually](#adding-routes-manually)
- [Custom class-loader](#custom-class-loader)
- [Integrating with php-di](#Integrating-with-php-di)
- [Parameters](#parameters)
- [Extending](#extending)
- [Help and support](#help-and-support)
- [Common issues and fixes](#common-issues-and-fixes)
- [Multiple routes matches? Which one has the priority?](#multiple-routes-matches-which-one-has-the-priority)
- [Parameters won't match or route not working with special characters](#parameters-wont-match-or-route-not-working-with-special-characters)
- [Using the router on sub-paths](#using-the-router-on-sub-paths)
- [Debugging](#debugging)
- [Creating unit-tests](#creating-unit-tests)
- [Debug information](#debug-information)
@@ -111,7 +125,7 @@ composer require pecee/simple-router
The goal of this project is to create a router that is more or less 100% compatible with the Laravel documentation, while remaining as simple as possible, and as easy to integrate and change without compromising either speed or complexity. Being lightweight is the #1 priority.
We've included a simple demo project for the router which can be found in the `demo-project` folder. This project should give you a basic understanding of how to setup and use simple-php-router project.
We've included a simple demo project for the router which can be found [here](https://github.com/skipperbent/simple-router-demo). This project should give you a basic understanding of how to setup and use simple-php-router project.
Please note that the demo-project only covers how to integrate the `simple-php-router` in a project without an existing framework. If you are using a framework in your project, the implementation might vary.
@@ -132,6 +146,7 @@ You can find the demo-project here: [https://github.com/skipperbent/simple-route
## Requirements
- PHP 7.1 or greater (version 3.x and below supports PHP 5.5+)
- PHP JSON extension enabled.
## Features
@@ -148,6 +163,8 @@ You can find the demo-project here: [https://github.com/skipperbent/simple-route
- Sub-domain routing
- Custom boot managers to rewrite urls to "nicer" ones.
- Input manager; easily manage `GET`, `POST` and `FILE` values.
- IP based restrictions.
- Easily extendable.
## Installation
@@ -229,17 +246,23 @@ Simply create a new `web.config` file in your projects `public` directory and pa
#### Troubleshooting
If you do not have a `favicon.ico` file in your project, you can get a `NotFoundHttpException` (404 - not found).
To add `favicon.ico` to the IIS ignore-list, add the following line to the `<conditions>` group:
```
<add input="{REQUEST_FILENAME}" negate="true" pattern="favicon.ico" ignoreCase="true" />
```
You can also make one exception for files with some extensions:
```
<add input="{REQUEST_FILENAME}" pattern="\.ico|\.png|\.css|\.jpg" negate="true" ignoreCase="true" />
```
If you are using `$_SERVER['ORIG_PATH_INFO']`, you will get `\index.php\` as part of the returned value. For example:
If you are using `$_SERVER['ORIG_PATH_INFO']`, you will get `\index.php\` as part of the returned value.
**Example:**
```
/index.php/test/mypage.php
```
@@ -281,8 +304,6 @@ We recommend that you add these helper functions to your project. These will all
To implement the functions below, simply copy the code to a new file and require the file before initializing the router or copy the `helpers.php` we've included in this library.
```php
<?php
use Pecee\SimpleRouter\SimpleRouter as Router;
use Pecee\Http\Url;
use Pecee\Http\Response;
@@ -330,14 +351,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();
@@ -383,10 +404,18 @@ Below is a very basic example of setting up a route. First parameter is the url
```php
SimpleRouter::get('/', function() {
return 'Hello world';
return 'Hello world';
});
```
### Class hinting
You can use class hinting to load a class & method like this:
```php
SimpleRouter::get('/', [MyClass::class, 'myMethod']);
```
### Available methods
Here you can see a list over all available routes:
@@ -406,11 +435,11 @@ Sometimes you might need to create a route that accepts multiple HTTP-verbs. If
```php
SimpleRouter::match(['get', 'post'], '/', function() {
// ...
// ...
});
SimpleRouter::any('foo', function() {
// ...
// ...
});
```
@@ -418,7 +447,7 @@ We've created a simple method which matches `GET` and `POST` which is most commo
```php
SimpleRouter::form('foo', function() {
// ...
// ...
});
```
@@ -430,7 +459,7 @@ You'll properly wondering by know how you parse parameters from your urls. For e
```php
SimpleRouter::get('/user/{id}', function ($userId) {
return 'User with id: ' . $userId;
return 'User with id: ' . $userId;
});
```
@@ -438,11 +467,12 @@ You may define as many route parameters as required by your route:
```php
SimpleRouter::get('/posts/{post}/comments/{comment}', function ($postId, $commentId) {
// ...
// ...
});
```
**Note:** Route parameters are always encased within {} braces and should consist of alphabetic characters. Route parameters may not contain a - character. Use an underscore (_) instead.
**Note:** Route parameters are always encased within `{` `}` braces and should consist of alphabetic characters. Route parameters can only contain certain characters like `A-Z`, `a-z`, `0-9`, `-` and `_`.
If your route contain other characters, please see [Custom regex for matching parameters](#custom-regex-for-matching-parameters).
### Optional parameters
@@ -450,11 +480,11 @@ Occasionally you may need to specify a route parameter, but make the presence of
```php
SimpleRouter::get('/user/{name?}', function ($name = null) {
return $name;
return $name;
});
SimpleRouter::get('/user/{name?}', function ($name = 'Simon') {
return $name;
return $name;
});
```
@@ -464,15 +494,21 @@ You may constrain the format of your route parameters using the where method on
```php
SimpleRouter::get('/user/{name}', function ($name) {
//
})->where('name', '[A-Za-z]+');
// ... do stuff
})->where([ 'name' => '[A-Za-z]+' ]);
SimpleRouter::get('/user/{id}', function ($id) {
//
})->where('id', '[0-9]+');
// ... do stuff
})->where([ 'id' => '[0-9]+' ]);
SimpleRouter::get('/user/{id}/{name}', function ($id, $name) {
//
// ... do stuff
})->where(['id' => '[0-9]+', 'name' => '[a-z]+']);
```
@@ -499,10 +535,12 @@ SimpleRouter::all('/ajax/abc/123', function($param1, $param2) {
### Custom regex for matching parameters
By default simple-php-router uses the `\w` regular expression when matching parameters.
By default simple-php-router uses the `[\w\-]+` regular expression. It will match `A-Z`, `a-z`, `0-9`, `-` and `_` characters in parameters.
This decision was made with speed and reliability in mind, as this match will match both letters, number and most of the used symbols on the internet.
However, sometimes it can be necessary to add a custom regular expression to match more advanced characters like `-` etc.
However, sometimes it can be necessary to add a custom regular expression to match more advanced characters like foreign letters `æ ø å` etc.
You can test your custom regular expression by using on the site [Regex101.com](https://www.regex101.com).
Instead of adding a custom regular expression to all your parameters, you can simply add a global regular expression which will be used on all the parameters on the route.
@@ -510,16 +548,16 @@ Instead of adding a custom regular expression to all your parameters, you can si
#### Example
This example will ensure that all parameters use the `[\w\-]+` regular expression when parsing.
This example will ensure that all parameters use the `[\w\-\æ\ø\å]+` (`a-z`, `A-Z`, `-`, `_`, `0-9`, `æ`, `ø`, `å`) regular expression when parsing.
```php
SimpleRouter::get('/path/{parameter}', 'VideoController@home', ['defaultParameterRegex' => '[\w\-]+']);
SimpleRouter::get('/path/{parameter}', 'VideoController@home', ['defaultParameterRegex' => '[\w\-\æ\ø\å]+']);
```
You can also apply this setting to a group if you need multiple routes to use your custom regular expression when parsing parameters.
```php
SimpleRouter::group(['defaultParameterRegex' => '[\w\-]+'], function() {
SimpleRouter::group(['defaultParameterRegex' => '[\w\-\æ\ø\å]+'], function() {
SimpleRouter::get('/path/{parameter}', 'VideoController@home');
@@ -598,6 +636,23 @@ SimpleRouter::group(['namespace' => 'Admin'], function () {
});
```
You can add parameters to the prefixes of your routes.
Parameters from your previous routes will be injected
into your routes after any route-required parameters, starting from oldest to newest.
```php
SimpleRouter::group(['prefix' => '/lang/{lang}'], function ($language) {
SimpleRouter::get('/about', function($language) {
// Will match /lang/da/about
});
});
```
### Subdomain-routing
Route groups may also be used to handle sub-domain routing. Sub-domains may be assigned route parameters just like route urls, allowing you to capture a portion of the sub-domain for usage in your route or controller. The sub-domain may be specified using the `domain` key on the group attribute array:
@@ -622,29 +677,37 @@ SimpleRouter::group(['prefix' => '/admin'], function () {
});
```
You can also use parameters in your groups:
```php
SimpleRouter::group(['prefix' => '/lang/{language}'], function ($language) {
SimpleRouter::get('/users', function ($language) {
// Matches The "/lang/da/users" URL
});
});
```
## Partial groups
Partial router groups has the same benefits as a normal group, but supports parameters and are only rendered once the url has matched.
Partial router groups has the same benefits as a normal group, but **are only rendered once the url has matched**
in contrast to a normal group which are always rendered in order to retrieve it's child routes.
Partial groups are therefore more like a hybrid of a traditional route with the benefits of a group.
This can be extremely useful in situations, where you only want special routes to be added, when a certain criteria or logic has been met.
This can be extremely useful in situations where you only want special routes to be added, but only when a certain criteria or logic has been met.
**NOTE:** Use partial groups with caution as routes added within are only rendered and available once the url of the partial-group has matched. This can cause `url()` not to find urls for the routes added within.
**NOTE:** Use partial groups with caution as routes added within are only rendered and available once the url of the partial-group has matched.
This can cause `url()` not to find urls for the routes added within before the partial-group has been matched and is rendered.
**Example:**
```php
SimpleRouter::partialGroup('/admin/{applicationId}', function ($applicationId) {
SimpleRouter::partialGroup('/plugin/{name}', function ($plugin) {
SimpleRouter::get('/', function($applicationId) {
// Matches The "/admin/applicationId" URL
});
// Add routes from plugin
});
```
## Form Method Spoofing
HTML forms do not support `PUT`, `PATCH` or `DELETE` actions. So, when defining `PUT`, `PATCH` or `DELETE` routes that are called from an HTML form, you will need to add a hidden `_method` field to the form. The value sent with the `_method` field will be used as the HTTP request method:
@@ -662,88 +725,6 @@ SimpleRouter::request()->getLoadedRoute();
request()->getLoadedRoute();
```
## Dependency injection
simple-router supports dependency injection using the [`php-di`](http://php-di.org/) library.
Dependency injection allows the framework to automatically "inject" (load) classes added as parameters. This can simplify your code, as you can avoid creating new instances of objects you are using often in your `Controllers` etc.
Here's a basic example of a controller class using dependency injection:
```php
namespace Demo\Controllers;
class DefaultController {
public function login(User $user): string
{
// ...
}
}
```
The example above will automatically create a new instance of the `User` from the `$user` parameter. This means that the `$user` class contains a new instance of the `User` class and we won't need to create a new instance our self.
**WARNING:** dependency injection can have some negative impact in performance. If you experience any performance issues, we recommend disabling this functionality.
### Enabling dependency injection
Dependency injection is disabled per default to avoid any performance issues.
Before enabling dependency injection, we recommend that you read the [Container configuration](http://php-di.org/doc/container-configuration.html) section of the php-di documentation. This section covers how to configure php-di to different environments and speed-up the performance.
#### Enabling for development environment
The example below should ONLY be used on a development environment.
```php
// Create our new php-di container
$container = (new \DI\ContainerBuilder())
->useAutowiring(true)
->build();
// Add our container to simple-router and enable dependency injection
SimpleRouter::enableDependencyInjection($container);
```
Please check the [More reading](#more-reading) section of the documentation for useful php-di links and tutorials.
#### Enabling for production environment
The example below compiles the injections, which can help speed up performance.
**Note:** You should change the `$cacheDir` to a cache-storage within your project.
```php
// Cache directory
$cacheDir = sys_get_temp_dir('simple-router');
// Create our new php-di container
$container = (new \DI\ContainerBuilder())
->enableCompilation($cacheDir)
->writeProxiesToFile(true, $cacheDir . '/proxies')
->useAutowiring(true)
->build();
// Add our container to simple-router and enable dependency injection
SimpleRouter::enableDependencyInjection($container);
```
Please check the [More reading](#more-reading) section of the documentation for useful php-di links and tutorials.
### More reading
For more information about dependency injection, configuration and settings - we recommend that you check the php-di documentation or some of the useful links we've gathered below.
#### Useful links
- [php-di documentation](http://php-di.org/doc/)
- [Understanding dependency injection](http://php-di.org/doc/understanding-di.html)
- [Best practices guide](http://php-di.org/doc/best-practices.html)
- [Configuring the container](http://php-di.org/doc/container-configuration.html)
- [Definitions](http://php-di.org/doc/definition.html)
## Other examples
You can find many more examples in the `routes.php` example-file below:
@@ -760,12 +741,17 @@ SimpleRouter::group(['middleware' => \Demo\Middlewares\Site::class, 'exceptionHa
SimpleRouter::get('/answers/{id}', 'ControllerAnswers@show', ['where' => ['id' => '[0-9]+']]);
/**
* Class hinting is supported too
*/
SimpleRouter::get('/answers/{id}', [ControllerAnswers::class, 'show'], ['where' => ['id' => '[0-9]+']]);
/**
* Restful resource (see IRestController interface for available methods)
*/
SimpleRouter::resource('/rest', ControllerRessource::class);
SimpleRouter::resource('/rest', ControllerResource::class);
/**
@@ -786,7 +772,6 @@ SimpleRouter::group(['middleware' => \Demo\Middlewares\Site::class, 'exceptionHa
});
SimpleRouter::get('/page/404', 'ControllerPage@notFound', ['as' => 'page.notfound']);
```
---
@@ -1136,9 +1121,7 @@ In the example below, we check if the current url contains the `/api` part.
```php
if(url()->contains('/api')) {
// ... do stuff
}
```
@@ -1156,86 +1139,82 @@ For more available methods please check the `Pecee\Http\Url` class.
# Input & parameters
simple-router offers libraries and helpers that makes it easy to manage and manipulate input-parameters like `$_POST`, `$_GET` and `$_FILE`.
## Using the Input class to manage parameters
We've added the `Input` class to easy access and manage parameters from your Controller-classes.
You can use the `InputHandler` class to easily access and manage parameters from your request. The `InputHandler` class offers extended features such as copying/moving uploaded files directly on the object, getting file-extension, mime-type etc.
### Get single parameter value:
### Get single parameter value
If items is grouped in the html, it will return an array of items.
```input($index, $defaultValue, ...$methods);```
**Note:** `get` will automatically trim the value and ensure that it's not empty. If it's empty the `$defaultValue` will be returned.
To quickly get a value from a parameter, you can use the `input` helper function.
This will automatically trim the value and ensure that it's not empty. If it's empty the `$defaultValue` will be returned instead.
**Note:**
This function returns a `string` unless the parameters are grouped together, in that case it will return an `array` of values.
**Example:**
This example matches both POST and GET request-methods and if name is empty the default-value "Guest" will be returned.
```php
$value = input($index, $defaultValue, $methods);
$name = input('name', 'Guest', 'post', 'get');
```
### Get parameter object
The example below will return an instance of `InputItem` or `InputFile` depending on the type.
When dealing with file-uploads it can be useful to retrieve the raw parameter object.
You can use this in your html as it will render the value of the item.
**Search for object with default-value across multiple or specific request-methods:**
If you want to compare value in your if statements, you have to use the `getValue` or use the `input()` helper function instead.
If items is grouped in the html, it will return an array of items.
The example below will return an `InputItem` object if the parameter was found or return the `$defaultValue`. If parameters are grouped, it will return an array of `InputItem` objects.
```php
$object = input()->get($index, $defaultValue = null, $methods = null);
$object = input()->find($index, $defaultValue = null, ...$methods);
```
### Return specific GET parameter (where name is the name of your parameter):
**Getting specific `$_GET` parameter as `InputItem` object:**
The example below will return an `InputItem` object if the parameter was found or return the `$defaultValue`. If parameters are grouped, it will return an array of `InputItem` objects.
```php
# -- match any (default) --
$object = input()->get($index, $defaultValue = null);
```
/*
* This is the recommended way to go for normal usage
* as it will strip empty values, ensuring that
* $defaultValue is returned if the value is empty.
*/
**Getting specific `$_POST` parameter as `InputItem` object:**
$id = input()->getValue($index, $defaultValue, $method);
The example below will return an `InputItem` object if the parameter was found or return the `$defaultValue`. If parameters are grouped, it will return an array of `InputItem` objects.
# -- shortcut to above --
```php
$object = input()->post($index, $defaultValue = null);
```
$id = input($index, $defaultValue, $method);
**Getting specific `$_FILE` parameter as `InputFile` object:**
# -- match specific --
The example below will return an `InputFile` object if the parameter was found or return the `$defaultValue`. If parameters are grouped, it will return an array of `InputFile` objects.
$value = input($index, $defaultValue, 'get');
$value = input($index, $defaultValue, 'post');
$value = input($index, $defaultValue, 'file');
# -- or --
$object = input()->findGet($index, $defaultValue);
$object = input()->findPost($index, $defaultValue);
$object = input()->findFile($index, $defaultValue);
# -- get the full object --
$object = input()->get($index, 'post', 'get');
```php
$object = input()->file($index, $defaultValue = null);
```
### Managing files
```php
/**
* In this small example we loop through a collection of files
* added on the page like this
* Loop through a collection of files uploaded from a form on the page like this
* <input type="file" name="images[]" />
*/
/* @var $image \Pecee\Http\Input\InputFile */
foreach(input('images', []) as $image)
foreach(input()->file('images', []) as $image)
{
if($image->getMime() === 'image/jpeg') {
if($image->getMime() === 'image/jpeg')
{
$destinationFilname = sprintf('%s.%s', uniqid(), $image->getExtension());
$image->move('/uploads/' . $destinationFilename);
$image->move(sprintf('/uploads/%s', $destinationFilename));
}
}
@@ -1244,10 +1223,10 @@ foreach(input('images', []) as $image)
### Get all parameters
```php
// Get all
# Get all
$values = input()->all();
// Only match certain keys
# Only match specific keys
$values = input()->all([
'company_name',
'user_id'
@@ -1261,6 +1240,7 @@ All object implements the `IInputItem` interface and will always contain these m
- `getValue()` - returns the value of the input.
`InputFile` has the same methods as above along with some other file-specific methods like:
- `getFilename` - get the filename.
- `getTmpName()` - get file temporary name.
- `getSize()` - get file size.
@@ -1268,16 +1248,9 @@ All object implements the `IInputItem` interface and will always contain these m
- `getContents()` - get file content.
- `getType()` - get mime-type for file.
- `getError()` - get file upload error.
- `hasError()` - returns `bool` if an error occurred while uploading (if getError is not 0).
- `hasError()` - returns `bool` if an error occurred while uploading (if `getError` is not 0).
- `toArray()` - returns raw array
Below example requires you to have the helper functions added. Please refer to the helper functions section in the documentation.
```php
/* Get parameter site_id or default-value 2 from either post-value or query-string */
$siteId = input('site_id', 2, ['post', 'get']);
```
---
# Events
@@ -1296,6 +1269,7 @@ All event callbacks will retrieve a `EventArgument` object as parameter. This ob
| `EVENT_ALL` | - | Fires when a event is triggered. |
| `EVENT_INIT` | - | Fires when router is initializing and before routes are loaded. |
| `EVENT_LOAD` | `loadedRoutes` | Fires when all routes has been loaded and rendered, just before the output is returned. |
| `EVENT_ADD_ROUTE` | `route`<br>`isSubRoute` | Fires when route is added to the router. `isSubRoute` is true when sub-route is rendered. |
| `EVENT_REWRITE` | `rewriteUrl`<br>`rewriteRoute` | Fires when a url-rewrite is and just before the routes are re-initialized. |
| `EVENT_BOOT` | `bootmanagers` | Fires when the router is booting. This happens just before boot-managers are rendered and before any routes has been loaded. |
| `EVENT_RENDER_BOOTMANAGER` | `bootmanagers`<br>`bootmanager` | Fires before a boot-manager is rendered. |
@@ -1417,6 +1391,80 @@ class DatabaseDebugHandler implements IEventHandler
# Advanced
## Disable multiple route rendering
By default the router will try to execute all routes that matches a given url. To stop the router from executing any further routes any method can return a value.
This behavior can be easily disabled by setting `SimpleRouter::enableMultiRouteRendering(false)` in your `routes.php` file. This is the same behavior as version 3 and below.
## Restrict access to IP
You can white and/or blacklist access to IP's using the build in `IpRestrictAccess` middleware.
Create your own custom Middleware and extend the `IpRestrictAccess` class.
The `IpRestrictAccess` class contains two properties `ipBlacklist` and `ipWhitelist` that can be added to your middleware to change which IP's that have access to your routes.
You can use `*` to restrict access to a range of ips.
```php
use \Pecee\Http\Middleware\IpRestrictAccess;
class IpBlockerMiddleware extends IpRestrictAccess
{
protected $ipBlacklist = [
'5.5.5.5',
'8.8.*',
];
protected $ipWhitelist = [
'8.8.2.2',
];
}
```
You can add the middleware to multiple routes by adding your [middleware to a group](#middleware).
## Setting custom base path
Sometimes it can be useful to add a custom base path to all of the routes added.
This can easily be done by taking advantage of the [Event Handlers](#events) support of the project.
```php
$basePath = '/basepath';
$eventHandler = new EventHandler();
$eventHandler->register(EventHandler::EVENT_ADD_ROUTE, function(EventArgument $event) use($basePath) {
$route = $event->route;
// Skip routes added by group as these will inherit the url
if(!$event->isSubRoute) {
return;
}
switch (true) {
case $route instanceof ILoadableRoute:
$route->prependUrl($basePath);
break;
case $route instanceof IGroupRoute:
$route->prependPrefix($basePath);
break;
}
});
TestRouter::addEventHandler($eventHandler);
```
In the example shown above, we create a new `EVENT_ADD_ROUTE` event that triggers, when a new route is added.
We skip all subroutes as these will inherit the url from their parent. Then, if the route is a group, we change the prefix
otherwise we change the url.
## Url rewriting
### Changing current route
@@ -1468,7 +1516,7 @@ class CustomRouterRules implement IRouterBootManager
// If the current url matches the rewrite url, we use our custom route
if($request->getUrl()->getPath() === $url) {
if($request->getUrl()->contains($url)) {
$request->setRewriteUrl($rule);
}
}
@@ -1516,6 +1564,140 @@ $route->setPrefix('v1');
$router->addRoute($route);
```
## Custom class loader
You can easily extend simple-router to support custom injection frameworks like php-di by taking advantage of the ability to add your custom class-loader.
Class-loaders must inherit the `IClassLoader` interface.
**Example:**
```php
class MyCustomClassLoader implements IClassLoader
{
/**
* Load class
*
* @param string $class
* @return object
* @throws NotFoundHttpException
*/
public function loadClass(string $class)
{
if (\class_exists($class) === false) {
throw new NotFoundHttpException(sprintf('Class "%s" does not exist', $class), 404);
}
return new $class();
}
/**
* 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], array_values($parameters));
}
/**
* Load closure
*
* @param Callable $closure
* @param array $parameters
* @return mixed
*/
public function loadClosure(Callable $closure, array $parameters)
{
return \call_user_func_array($closure, array_values($parameters));
}
}
```
Next, we need to configure our `routes.php` so the router uses our `MyCustomClassLoader` class for loading classes. This can be done by adding the following line to your `routes.php` file.
```php
SimpleRouter::setCustomClassLoader(new MyCustomClassLoader());
```
### Integrating with php-di
php-di support was discontinued by version 4.3, however you can easily add it again by creating your own class-loader like the example below:
```php
use Pecee\SimpleRouter\Exceptions\ClassNotFoundHttpException;
class MyCustomClassLoader implements IClassLoader
{
protected $container;
public function __construct()
{
// Create our new php-di container
$this->container = (new \DI\ContainerBuilder())
->useAutowiring(true)
->build();
}
/**
* Load class
*
* @param string $class
* @return object
* @throws NotFoundHttpException
*/
public function loadClass(string $class)
{
if (class_exists($class) === false) {
throw new NotFoundHttpException(sprintf('Class "%s" does not exist', $class), 404);
}
try {
return $this->container->get($class);
} catch (\Exception $e) {
throw new NotFoundHttpException($e->getMessage(), (int)$e->getCode(), $e->getPrevious());
}
}
/**
* Called when loading class method
* @param object $class
* @param string $method
* @param array $parameters
* @return object
*/
public function loadClassMethod($class, string $method, array $parameters)
{
try {
return $this->container->call([$class, $method], $parameters);
} catch (\Exception $e) {
throw new NotFoundHttpException($e->getMessage(), (int)$e->getCode(), $e->getPrevious());
}
}
/**
* Load closure
*
* @param Callable $closure
* @param array $parameters
* @return mixed
*/
public function loadClosure(callable $closure, array $parameters)
{
try {
return $this->container->call($closure, $parameters);
} catch (\Exception $e) {
throw new NotFoundHttpException($e->getMessage(), (int)$e->getCode(), $e->getPrevious());
}
}
}
```
## Parameters
This section contains advanced tips & tricks on extending the usage for parameters.
@@ -1559,6 +1741,28 @@ This section will go into details on how to debug the router and answer some of
This section will go over common issues and how to resolve them.
### Parameters won't match or route not working with special characters
Often people experience this issue when one or more parameters contains special characters. The router uses a sparse regular-expression that matches letters from a-z along with numbers when matching parameters, to improve performance.
All other characters has to be defined via the `defaultParameterRegex` option on your route.
You can read more about adding your own custom regular expression for matching parameters by [clicking here](#custom-regex-for-matching-parameters).
### Multiple routes matches? Which one has the priority?
The router will match routes in the order they're added and will render multiple routes, if they match.
If you want the router to stop when a route is matched, you simply return a value in your callback or stop the execution manually (using `response()->json()` etc.) or simply by returning a result.
Any returned objects that implements the `__toString()` magic method will also prevent other routes from being rendered.
If you want the router only to execute one route per request, you can [disabling multiple route rendering](#disable-multiple-route-rendering).
### Using the router on sub-paths
Please refer to [Setting custom base path](#setting-custom-base-path) part of the documentation.
## Debugging
This section will show you how to write unit-tests for the router, view useful debugging information and answer some of the frequently asked questions.
@@ -1797,4 +2001,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
SOFTWARE.
+7 -2
View File
@@ -28,12 +28,17 @@
],
"require": {
"php": ">=7.1",
"php-di/php-di": "^6.0"
"ext-json": "*"
},
"require-dev": {
"phpunit/phpunit": "^6.0",
"phpunit/phpunit": "^7",
"mockery/mockery": "^1"
},
"scripts": {
"test": [
"phpunit tests"
]
},
"autoload": {
"psr-4": {
"Pecee\\": "src/Pecee/"
+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();
+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
{
}
+4 -4
View File
@@ -9,14 +9,14 @@ 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;
public function getValue();
public function setValue(string $value): self;
public function setValue($value): self;
public function __toString();
public function __toString(): string;
}
+10 -10
View File
@@ -49,7 +49,7 @@ class InputFile implements IInputItem
return (new static($values['index']))
->setSize((int)$values['size'])
->setError($values['error'])
->setError((int)$values['error'])
->setType($values['type'])
->setTmpName($values['tmp_name'])
->setFilename($values['name']);
@@ -140,7 +140,7 @@ class InputFile implements IInputItem
*
* @return string
*/
public function getName(): string
public function getName(): ?string
{
return $this->name;
}
@@ -177,7 +177,7 @@ class InputFile implements IInputItem
*
* @return string mixed
*/
public function getFilename(): string
public function getFilename(): ?string
{
return $this->filename;
}
@@ -216,11 +216,11 @@ class InputFile implements IInputItem
/**
* Get upload-error code.
*
* @return string
* @return int
*/
public function getError(): string
public function getError(): int
{
return $this->errors;
return (int)$this->errors;
}
/**
@@ -256,21 +256,21 @@ class InputFile implements IInputItem
return $this;
}
public function __toString()
public function __toString(): string
{
return $this->getTmpName();
}
public function getValue(): string
public function getValue()
{
return $this->getFilename();
}
/**
* @param string $value
* @param mixed $value
* @return static
*/
public function setValue(string $value): IInputItem
public function setValue($value): IInputItem
{
$this->filename = $value;
+271 -103
View File
@@ -10,23 +10,41 @@ class InputHandler
/**
* @var array
*/
public $get = [];
protected $get = [];
/**
* @var array
*/
public $post = [];
protected $post = [];
/**
* @var array
*/
public $file = [];
protected $file = [];
/**
* @var Request
*/
protected $request;
/**
* Original post variables
* @var array
*/
protected $originalPost = [];
/**
* Original get/params variables
* @var array
*/
protected $originalParams = [];
/**
* Get original file variables
* @var array
*/
protected $originalFile = [];
/**
* Input constructor.
* @param Request $request
@@ -45,39 +63,60 @@ 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;
}
}
}
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 +126,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 +139,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, &$index, $original): array
{
$originalIndex = $index[0];
array_shift($index);
@@ -110,7 +156,7 @@ class InputHandler
foreach ($values as $key => $value) {
if (\is_array($original['name'][$key]) === false) {
if (is_array($original['name'][$key]) === false) {
try {
@@ -138,7 +184,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 +197,121 @@ 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
*
* @param string $index
* @param array ...$methods
* @return bool
*/
public function exists(string $index, ...$methods): bool
{
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
* @return InputItem|array|string|null
*/
public function findPost(string $index, ?string $defaultValue = null)
public function post(string $index, ?string $defaultValue = null)
{
return $this->post[$index] ?? $defaultValue;
}
@@ -188,9 +321,9 @@ class InputHandler
*
* @param string $index
* @param string|null $defaultValue
* @return InputFile|string
* @return InputFile|array|string|null
*/
public function findFile(string $index, ?string $defaultValue = null)
public function file(string $index, ?string $defaultValue = null)
{
return $this->file[$index] ?? $defaultValue;
}
@@ -200,91 +333,126 @@ class InputHandler
*
* @param string $index
* @param string|null $defaultValue
* @return InputItem|string
* @return InputItem|array|string|null
*/
public function findGet(string $index, ?string $defaultValue = null)
public function get(string $index, ?string $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;
}
}
+17 -9
View File
@@ -2,13 +2,16 @@
namespace Pecee\Http\Input;
class InputItem implements IInputItem
use ArrayIterator;
use IteratorAggregate;
class InputItem implements IInputItem, IteratorAggregate
{
public $index;
public $name;
public $value;
public function __construct(string $index, ?string $value = null)
public function __construct(string $index, $value = null)
{
$this->index = $index;
$this->value = $value;
@@ -35,7 +38,7 @@ class InputItem implements IInputItem
/**
* @return string
*/
public function getName(): string
public function getName(): ?string
{
return $this->name;
}
@@ -53,28 +56,33 @@ 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 __toString(): string
{
return (string)$this->value;
$value = $this->getValue();
return (is_array($value) === true) ? json_encode($value) : $value;
}
public function getIterator(): ArrayIterator
{
return new ArrayIterator($this->getValue());
}
}
+36 -15
View File
@@ -9,15 +9,24 @@ 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';
/**
* Urls to ignore. You can use * to exclude all sub-urls on a given path.
* For example: /admin/*
* @var array|null
*/
protected $except;
/**
* Urls to include. Can be used to include urls from a certain path.
* @var array|null
*/
protected $include;
protected $tokenProvider;
/**
* BaseCsrfVerifier constructor.
* @throws \Pecee\Http\Security\Exceptions\SecurityException
*/
public function __construct()
{
@@ -31,24 +40,38 @@ class BaseCsrfVerifier implements IMiddleware
*/
protected function skip(Request $request): bool
{
if ($this->except === null || \count($this->except) === 0) {
if ($this->except === null || 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) {
if(is_array($this->include) === true && count($this->include) > 0) {
foreach($this->include as $includeUrl) {
$includeUrl = rtrim($includeUrl, '/');
if ($includeUrl[strlen($includeUrl) - 1] === '*') {
$includeUrl = rtrim($includeUrl, '*');
$skip = !$request->getUrl()->contains($includeUrl);
break;
}
$skip = !($includeUrl === rtrim($request->getUrl()->getRelativeUrl(false), '/'));
}
}
if($skip === false) {
continue;
}
return true;
}
}
@@ -64,13 +87,12 @@ class BaseCsrfVerifier implements IMiddleware
*/
public function handle(Request $request): void
{
if ($this->skip($request) === false && $request->isPostBack() === 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'
Request::$requestTypesPost
);
if ($this->tokenProvider->validate((string)$token) === false) {
@@ -81,7 +103,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 $ipBlacklist = [];
protected $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);
}
}
}
+151 -23
View File
@@ -4,12 +4,52 @@ 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 $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 $requestTypesPost = [
self::REQUEST_TYPE_POST,
self::REQUEST_TYPE_PUT,
self::REQUEST_TYPE_PATCH,
self::REQUEST_TYPE_DELETE,
];
/**
* Additional data
*
@@ -23,6 +63,12 @@ class Request
*/
protected $headers = [];
/**
* Request ContentType
* @var string
*/
protected $contentType;
/**
* Request host
* @var string
@@ -77,16 +123,16 @@ 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'))));
$this->setUrl(new Url($this->getFirstHeader(['unencoded-url', '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
@@ -146,6 +192,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 +212,23 @@ 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 = ['remote-addr'];
if($safeMode === false) {
$headers = array_merge($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');
}
return $this->getHeader('remote-addr');
return $this->getFirstHeader($headers);
}
/**
@@ -204,14 +263,72 @@ class Request
/**
* Get header value by name
*
* @param string $name
* @param string|null $defaultValue
* @param string $name Name of the header.
* @param string|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, $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 +347,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);
}
@@ -245,6 +362,16 @@ class Request
return (strtolower($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);
}
/**
* Get accept formats
* @return array
@@ -264,6 +391,10 @@ class Request
if ($this->url->getHost() === null) {
$this->url->setHost((string)$this->getHost());
}
if($this->isSecure() === true) {
$this->url->setScheme('https');
}
}
/**
@@ -279,7 +410,7 @@ class Request
*/
public function setMethod(string $method): void
{
$this->method = $method;
$this->method = strtolower($method);
}
/**
@@ -348,7 +479,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 +501,6 @@ class Request
public function setLoadedRoutes(array $routes): self
{
$this->loadedRoutes = $routes;
return $this;
}
@@ -383,7 +513,6 @@ class Request
public function addLoadedRoute(ILoadableRoute $route): self
{
$this->loadedRoutes[] = $route;
return $this;
}
@@ -406,11 +535,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;
}
+5 -4
View File
@@ -2,6 +2,7 @@
namespace Pecee\Http;
use JsonSerializable;
use Pecee\Exceptions\InvalidArgumentException;
class Response
@@ -30,7 +31,7 @@ class Response
* Redirect the response
*
* @param string $url
* @param int $httpCode
* @param ?int $httpCode
*/
public function redirect(string $url, ?int $httpCode = null): void
{
@@ -85,14 +86,14 @@ class Response
/**
* Json encode
* @param array|\JsonSerializable $value
* @param int $options JSON options Bitmask consisting of JSON_HEX_QUOT, JSON_HEX_TAG, JSON_HEX_AMP, JSON_HEX_APOS, JSON_NUMERIC_CHECK, JSON_PRETTY_PRINT, JSON_UNESCAPED_SLASHES, JSON_FORCE_OBJECT, JSON_PRESERVE_ZERO_FRACTION, JSON_UNESCAPED_UNICODE, JSON_PARTIAL_OUTPUT_ON_ERROR.
* @param 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
{
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.');
}
@@ -2,6 +2,7 @@
namespace Pecee\Http\Security;
use Exception;
use Pecee\Http\Security\Exceptions\SecurityException;
class CookieTokenProvider implements ITokenProvider
@@ -17,7 +18,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 +35,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 +64,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 +74,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
{
}
+72 -39
View File
@@ -2,9 +2,10 @@
namespace Pecee\Http;
use JsonSerializable;
use Pecee\Http\Exceptions\MalformedUrlException;
class Url
class Url implements JsonSerializable
{
private $originalUrl;
@@ -14,13 +15,13 @@ class Url
private $username;
private $password;
private $path;
private $params;
private $params = [];
private $fragment;
/**
* Url constructor.
*
* @param string $url
* @param ?string $url
* @throws MalformedUrlException
*/
public function __construct(?string $url)
@@ -43,9 +44,7 @@ class Url
$this->fragment = $data['fragment'] ?? null;
if (isset($data['query']) === true) {
$params = [];
parse_str($data['query'], $params);
$this->setParams($params);
$this->setQueryString($data['query']);
}
}
}
@@ -240,6 +239,23 @@ class Url
return $this;
}
/**
* Set raw query-string parameters as string
*
* @param string $queryString
* @return static
*/
public function setQueryString(string $queryString): self
{
$params = [];
if(parse_str($queryString, $params) !== false) {
return $this->setParams($params);
}
return $this;
}
/**
* Get query-string params as string
*
@@ -314,25 +330,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 +372,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 +386,7 @@ class Url
{
$encodedUrl = preg_replace_callback(
'/[^:\/@?&=#]+/u',
function ($matches) {
static function ($matches) {
return urlencode($matches[0]);
},
$url
@@ -394,10 +410,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) {
return (trim($item) !== '');
});
}
@@ -411,14 +427,18 @@ class Url
/**
* Returns the relative url
*
* @param bool $includeParams
* @return string
*/
public function getRelativeUrl(): string
public function getRelativeUrl($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,9 +447,10 @@ class Url
/**
* Returns the absolute url
*
* @param bool $includeParams
* @return string
*/
public function getAbsoluteUrl(): string
public function getAbsoluteUrl($includeParams = true): string
{
$scheme = $this->scheme !== null ? $this->scheme . '://' : '';
$host = $this->host ?? '';
@@ -438,10 +459,22 @@ class Url
$pass = $this->password !== null ? ':' . $this->password : '';
$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 mixed 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->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 object
*/
public function loadClassMethod($class, string $method, array $parameters)
{
return call_user_func_array([$class, $method], array_values($parameters));
}
/**
* Load closure
*
* @param \Closure $closure
* @param Callable $closure
* @param array $parameters
* @return mixed
* @throws NotFoundHttpException
*/
public function loadClosure(\Closure $closure, array $parameters)
public function loadClosure(Callable $closure, array $parameters)
{
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 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 object
*/
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);
}
@@ -2,6 +2,7 @@
namespace Pecee\SimpleRouter\Event;
use InvalidArgumentException;
use Pecee\Http\Request;
use Pecee\SimpleRouter\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)
{
throw new \InvalidArgumentException('Not supported');
throw new InvalidArgumentException('Not supported');
}
/**
@@ -0,0 +1,38 @@
<?php
namespace Pecee\SimpleRouter\Exceptions;
use Throwable;
class ClassNotFoundHttpException extends NotFoundHttpException
{
protected $class;
protected $method;
public function __construct(string $class, ?string $method = null, $message = "", $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;
/**
@@ -17,19 +19,19 @@ class CallbackExceptionHandler implements IExceptionHandler
protected $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;
public function __construct()
{
$this->callback = function (EventArgument $argument) {
$this->callback = static function (EventArgument $argument) {
// todo: log in database
};
}
@@ -52,9 +53,9 @@ class DebugEventHandler implements IEventHandler
/**
* 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.
*/
@@ -95,6 +101,7 @@ class EventHandler implements IEventHandler
self::EVENT_ALL,
self::EVENT_INIT,
self::EVENT_LOAD,
self::EVENT_ADD_ROUTE,
self::EVENT_REWRITE,
self::EVENT_BOOT,
self::EVENT_RENDER_BOOTMANAGER,
@@ -119,10 +126,10 @@ class EventHandler implements IEventHandler
* 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;
}
+10 -2
View File
@@ -29,7 +29,7 @@ interface IGroupRoute extends IRoute
* @param array $handlers
* @return static
*/
public function setExceptionHandlers(array $handlers);
public function setExceptionHandlers(array $handlers): self;
/**
* Get exception-handlers for group
@@ -53,13 +53,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;
}
+29 -24
View File
@@ -34,7 +34,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 +42,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);
@@ -60,7 +60,7 @@ abstract class LoadableRoute extends Route implements ILoadableRoute
return null;
}
return ((bool)preg_match($this->regex, $request->getHost() . $url) !== false);
return ((bool)preg_match($this->regex, $url) !== false);
}
/**
@@ -85,6 +85,17 @@ abstract class LoadableRoute extends Route implements ILoadableRoute
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;
@@ -105,13 +116,10 @@ abstract class LoadableRoute extends Route implements ILoadableRoute
$group = $this->getGroup();
if ($group !== null && \count($group->getDomains()) !== 0) {
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,14 +132,14 @@ 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];
}
}
@@ -140,13 +148,12 @@ abstract class LoadableRoute extends Route implements ILoadableRoute
/* Add parameter to the correct position */
$url = str_ireplace([sprintf($param1, $param), sprintf($param2, $param)], $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);
return rtrim($url, '/') . '/';
return rtrim('/' . ltrim($url, '/'), '/') . '/';
}
/**
@@ -154,7 +161,7 @@ abstract class LoadableRoute extends Route implements ILoadableRoute
*
* @return string
*/
public function getName(): string
public function getName(): ?string
{
return $this->name;
}
@@ -176,7 +183,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;
@@ -222,15 +229,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 +246,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);
}
}
+115 -99
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
@@ -67,7 +51,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 +60,73 @@ 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) {
return ($var !== null);
});
}
/* Render callback function */
if (\is_callable($callback) === true) {
if (is_callable($callback) === true) {
$router->debug('Executing callback');
/* 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, $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, '/');
$urlRegex = '';
$parameters = [];
if ((bool)preg_match_all('/' . $regex . '/u', $route, $parameters) === false) {
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('/((-?\/?){[^}]+})/', $route) as $key => $t) {
$regex = '';
if ($key < \count($parameters[1])) {
if ($key < count($parameters[1])) {
$name = $parameters[1][$key];
@@ -143,26 +134,17 @@ 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) {
if (trim($urlRegex) === '' || (bool)preg_match(sprintf($this->urlRegex, $urlRegex), $url, $matches) === false) {
return null;
}
@@ -170,12 +152,27 @@ 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;
// Ignore parent parameters
if(isset($groupParameters[$name]) === true) {
$lastParams[$name] = $matches[$name];
continue;
}
$values[$name] = (isset($matches[$name]) === true && $matches[$name] !== '') ? $matches[$name] : null;
}
$values = array_merge($values, $lastParams);
}
$this->originalParameters = $values;
return $values;
}
@@ -188,11 +185,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 +244,7 @@ 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 +263,7 @@ abstract class Route implements IRoute
/**
* Set callback
*
* @param string $callback
* @param string|array|\Closure $callback
* @return static
*/
public function setCallback($callback): IRoute
@@ -288,7 +283,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 +298,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 +313,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;
}
@@ -337,7 +340,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;
@@ -370,15 +373,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;
}
@@ -392,35 +395,35 @@ abstract class Route implements IRoute
/**
* 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 ($this->namespace === null && 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']);
}
return $this;
@@ -453,9 +456,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 +475,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 +490,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 +498,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 +512,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 +550,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 +567,25 @@ 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;
}
}
@@ -35,7 +35,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;
}
}
@@ -64,10 +64,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 = (string)substr($method, strlen($requestType));
break;
}
}
@@ -77,7 +77,7 @@ class RouteController extends LoadableRoute implements IControllerRoute
$group = $this->getGroup();
if ($group !== null && \count($group->getDomains()) !== 0) {
if ($group !== null && count($group->getDomains()) !== 0) {
$url .= '//' . $group->getDomains()[0];
}
@@ -86,7 +86,7 @@ class RouteController extends LoadableRoute implements IControllerRoute
return '/' . trim($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) {
return false;
@@ -95,22 +95,22 @@ class RouteController extends LoadableRoute implements IControllerRoute
/* 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 +167,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);
}
}
+53 -22
View File
@@ -7,6 +7,7 @@ use Pecee\SimpleRouter\Handlers\IExceptionHandler;
class RouteGroup extends Route implements IGroupRoute
{
protected $urlRegex = '/^%s\/?/u';
protected $prefix;
protected $name;
protected $domains = [];
@@ -20,18 +21,21 @@ class RouteGroup extends Route implements IGroupRoute
*/
public function matchDomain(Request $request): bool
{
if ($this->domains === null || \count($this->domains) === 0) {
if ($this->domains === null || count($this->domains) === 0) {
return true;
}
foreach ($this->domains as $domain) {
// If domain has no parameters but matches
if ($domain === $request->getHost()) {
return true;
}
$parameters = $this->parseParameters($domain, $request->getHost(), '.*');
if ($parameters !== null && \count($parameters) !== 0) {
if ($parameters !== null && count($parameters) !== 0) {
$this->parameters = $parameters;
return true;
}
}
@@ -46,14 +50,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);
/* 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 . '}', $value, $parsedPrefix);
}
/* Skip if prefix doesn't match */
if ($this->prefix !== null && stripos($url, $this->prefix) === false) {
if ($this->prefix !== null && stripos($url, $parsedPrefix) === false) {
return false;
}
@@ -123,13 +146,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.
*
@@ -143,28 +177,27 @@ class RouteGroup extends Route implements IGroupRoute
/**
* 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['prefix']) === true) {
$this->setPrefix($values['prefix'] . $this->prefix);
if (isset($settings['prefix']) === true) {
$this->setPrefix($settings['prefix'] . $this->prefix);
}
if ($merge === false && isset($values['exceptionHandler']) === true) {
$this->setExceptionHandlers((array)$values['exceptionHandler']);
if ($merge === false && isset($settings['exceptionHandler']) === true) {
$this->setExceptionHandlers((array)$settings['exceptionHandler']);
}
if ($merge === false && isset($values['domain']) === true) {
$this->setDomains((array)$values['domain']);
if ($merge === false && isset($settings['domain']) === true) {
$this->setDomains((array)$settings['domain']);
}
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;
@@ -173,9 +206,7 @@ class RouteGroup extends Route implements IGroupRoute
$this->name = $name;
}
parent::setSettings($values, $merge);
return $this;
return parent::setSettings($settings, $merge);
}
/**
@@ -195,7 +226,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);
}
}
+18 -20
View File
@@ -76,14 +76,14 @@ class RouteResource extends LoadableRoute implements IControllerRoute
return $this->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) {
return false;
@@ -92,7 +92,7 @@ class RouteResource extends LoadableRoute implements IControllerRoute
/* 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;
}
@@ -115,32 +115,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']);
}
@@ -190,7 +190,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 +210,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);
}
}
+1 -1
View File
@@ -12,7 +12,7 @@ class RouteUrl extends LoadableRoute
$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;
+105 -79
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;
@@ -110,6 +111,13 @@ class Router
*/
protected $classLoader;
/**
* When enabled the router will render all routes that matches.
* When disabled the router will stop execution when first route is found.
* @var bool
*/
protected $renderMultipleRoutes = true;
/**
* Router constructor.
*/
@@ -151,18 +159,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,19 +185,18 @@ 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);
}
}
@@ -216,7 +226,7 @@ class Router
/* @var $route IRoute */
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,7 +239,7 @@ class Router
if ($route->matchRoute($url, $this->request) === true) {
/* Add exception handlers */
if (\count($route->getExceptionHandlers()) !== 0) {
if (count($route->getExceptionHandlers()) !== 0) {
/** @noinspection AdditionOperationOnArraysInspection */
$exceptionHandlers += $route->getExceptionHandlers();
}
@@ -260,13 +270,20 @@ class Router
/**
* 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,7 +293,7 @@ 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,
@@ -289,13 +306,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 +313,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
{
@@ -342,13 +352,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 +366,7 @@ 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));
/* If the route matches */
if ($route->matchRoute($url, $this->request) === true) {
@@ -366,9 +376,14 @@ 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;
}
@@ -392,28 +407,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 !== null) {
return $routeOutput;
}
$output = $this->handleRouteRewrite($key, $url);
if ($output !== null) {
return $output;
}
} else {
$output = $this->handleRouteRewrite($key, $url);
return $output ?? $routeOutput;
}
}
}
} catch (\Exception $e) {
} catch (Exception $e) {
$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));
$this->handleException(new NotFoundHttpException($message, 403));
}
if (\count($this->request->getLoadedRoutes()) === 0) {
if (count($this->request->getLoadedRoutes()) === 0) {
$rewriteUrl = $this->request->getRewriteUrl();
@@ -438,9 +460,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,7 +477,9 @@ class Router
}
if ($this->request->getRewriteUrl() !== $url) {
unset($this->processedRoutes[$key]);
$this->request->setHasPendingRewrite(false);
$this->fireEvents(EventHandler::EVENT_REWRITE, [
@@ -470,14 +494,14 @@ 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,
@@ -487,7 +511,7 @@ class Router
/* @var $handler IExceptionHandler */
foreach ($this->exceptionHandlers as $key => $handler) {
if (\is_object($handler) === false) {
if (is_object($handler) === false) {
$handler = new $handler();
}
@@ -497,14 +521,13 @@ class Router
'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');
@@ -527,7 +550,7 @@ class Router
return $this->routeRequest();
}
} catch (\Exception $e) {
} catch (Exception $e) {
}
@@ -563,14 +586,14 @@ 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 (is_string($name) === true && strpos($name, '@') !== false) {
[$controller, $method] = array_map('strtolower', explode('@', $name));
if ($controller === strtolower($route->getClass()) && $method === strtolower($route->getMethod())) {
@@ -582,7 +605,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($name) === true && 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)) {
@@ -622,11 +645,10 @@ 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,
@@ -634,7 +656,7 @@ class Router
'getParams' => $getParams,
]);
if ($getParams !== null && \is_array($getParams) === false) {
if ($getParams !== null && is_array($getParams) === false) {
throw new InvalidArgumentException('Invalid type for getParams. Must be array or null');
}
@@ -643,11 +665,7 @@ class Router
}
/* 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) {
@@ -666,18 +684,20 @@ class Router
->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()
->setPath($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 */
@@ -803,26 +823,20 @@ 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;
}
/**
@@ -839,13 +853,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;
}
/**
@@ -864,9 +875,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;
}
@@ -918,4 +929,19 @@ class Router
return $this->debugList;
}
/**
* 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;
}
}
+96 -90
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;
@@ -56,10 +56,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 +79,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,96 +163,111 @@ 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, function () use ($to, $httpCode) {
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
*/
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
*/
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
*/
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
*/
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
*/
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
*/
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
* @param Closure $callback
* @return RouteGroup
* @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) {
if (is_callable($callback) === false) {
throw new InvalidArgumentException('Invalid callback provided. Only functions or methods supported');
}
@@ -267,14 +285,14 @@ 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
* @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) {
if (is_callable($callback) === false) {
throw new InvalidArgumentException('Invalid callback provided. Only functions or methods supported');
}
@@ -293,14 +311,14 @@ class SimpleRouter
* Alias for the form method
*
* @param string $url
* @param callable $callback
* @param string|array|Closure $callback
* @param array|null $settings
* @see SimpleRouter::form
* @return RouteUrl
* @see SimpleRouter::form
*/
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 +326,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
* @see SimpleRouter::form
* @return RouteUrl
* @see SimpleRouter::form
*/
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,7 +344,7 @@ class SimpleRouter
*
* @param array $requestMethods
* @param string $url
* @param string|\Closure $callback
* @param string|array|Closure $callback
* @param array|null $settings
* @return RouteUrl|IRoute
*/
@@ -331,37 +352,31 @@ class SimpleRouter
{
$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)
{
$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 +387,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)
{
$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,27 +406,24 @@ 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)
{
$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();
@@ -447,26 +456,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
{
@@ -511,39 +513,43 @@ class SimpleRouter
{
if (static::$defaultNamespace !== null) {
$callback = $route->getCallback();
$ns = static::$defaultNamespace;
$namespace = $route->getNamespace();
/* 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;
if ($namespace !== null) {
// Don't overwrite namespaces that starts with \
if ($namespace[0] !== '\\') {
$ns .= '\\' . $namespace;
} else {
$ns = $namespace;
}
$route->setDefaultNamespace($namespace);
}
$route->setNamespace($ns);
}
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], ['result' => true]);
}
public function loadClosure(callable $closure, array $parameters)
{
return call_user_func_array($closure, ['result' => true]);
}
}
@@ -0,0 +1,18 @@
<?php
class DummyCsrfVerifier extends \Pecee\Http\Middleware\BaseCsrfVerifier {
protected $except = [
'/exclude-page',
'/exclude-all/*',
];
protected $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 $ipBlacklist = [
'5.5.5.5',
'8.8.*',
];
protected $ipWhitelist = [
'8.8.2.2',
];
}
@@ -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;
}
}
+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));
}
}
+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
}
@@ -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);
}
}
@@ -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
{
/**
+103 -12
View File
@@ -6,10 +6,28 @@ 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 +101,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,8 +166,8 @@ 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');
});
});
@@ -127,17 +180,33 @@ class RouterRouteTest extends \PHPUnit\Framework\TestCase
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 +218,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 +226,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 +235,26 @@ 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 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);
}
}
+184 -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';
});
@@ -30,8 +30,8 @@ class RouterUrlTest extends \PHPUnit\Framework\TestCase
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());
@@ -78,10 +78,11 @@ class RouterUrlTest extends \PHPUnit\Framework\TestCase
public function testSimilarUrls()
{
// 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());
@@ -170,4 +171,181 @@ class RouterUrlTest extends \PHPUnit\Framework\TestCase
}
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);
}
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);
}
}
+12 -6
View File
@@ -3,6 +3,11 @@
class TestRouter extends \Pecee\SimpleRouter\SimpleRouter
{
public function __construct()
{
static::request()->setHost('testhost.com');
}
public static function debugNoReset($testUrl, $testMethod = 'get')
{
$request = static::request();
@@ -13,7 +18,7 @@ class TestRouter extends \Pecee\SimpleRouter\SimpleRouter
static::start();
}
public static function debug($testUrl, $testMethod = 'get')
public static function debug($testUrl, $testMethod = 'get', bool $reset = true)
{
try {
static::debugNoReset($testUrl, $testMethod);
@@ -22,19 +27,20 @@ class TestRouter extends \Pecee\SimpleRouter\SimpleRouter
throw $e;
}
static::router()->reset();
if($reset === true) {
static::router()->reset();
}
}
public static function debugOutput($testUrl, $testMethod = 'get')
public static function debugOutput($testUrl, $testMethod = 'get', bool $reset = true)
{
$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;