Compare commits

...

581 Commits

Author SHA1 Message Date
Simon Sessingø c4cf878e97 Merge pull request #494 from skipperbent/v4-development
Version 4.3.0.0
2021-03-29 01:19:02 +02:00
Simon Sessingø 5b99e98a24 Merge pull request #524 from skipperbent/v4-cleanup
[CLEANUP] Cleaned up code
2021-03-29 00:07:12 +02:00
Simon Sessingø c916a1dd2e [CLEANUP] Cleaned up code
- Change variable $values to $settings in Route::setSettings method.
- Added return types to methods.
- Added type to method variables.
- Change ClassNotFoundException so required parameters are first.
2021-03-29 00:00:01 +02:00
Simon Sessingø 9ed2d2b8d1 Updated Request::isPostBack to return true if request-method could contain data in body. 2021-03-28 23:32:33 +02:00
Simon Sessingø caf30cb056 Merge pull request #523 from skipperbent/v4-isPostBack
[FEATURE] Added Request::isPostBack helper method
2021-03-28 04:25:54 +02:00
Simon Sessingø 6ccd06911e Fixed possible bug causing InputHandler not to get the correct request-method + simplified Request class. 2021-03-28 04:24:33 +02:00
Simon Sessingø e5eb966780 Added Request::isPostBack helper method 2021-03-28 04:05:09 +02:00
Simon Sessingø 9e19cbfb71 Merge pull request #521 from skipperbent/v4-fix-group-domain
[BUGFIX] Fixed group not matching domain with no parameters (issue: #468)
2021-03-26 01:35:37 +01:00
Simon Sessingø 073479f9dd [BUGFIX] Fixed group not matching domain with no parameters (issue: #468).
- Added unit-tests
2021-03-26 01:32:01 +01:00
Simon Sessingø 39ee1bb7cd Added missing phpDocs for parameters. 2021-03-25 14:31:04 +01:00
Simon Sessingø cf1c59aee0 Changed parameter name and added parameter phpDocs description. 2021-03-25 14:24:50 +01:00
Simon Sessingø d9cfa71534 Fixed naming in unit-tests. 2021-03-25 14:21:30 +01:00
Simon Sessingø cf6750aaf3 [FEATURE] Optimised Request::getIp method
- Added unit-tests for Request::getIp
- Optimised existing RequestTest unit-tests.
2021-03-25 14:18:56 +01:00
Simon Sessingø 11313a31dc Merge pull request #504 from DeveloperMarius/get-ip-proxy
Get the ip when the user is using a proxy
2021-03-25 14:17:55 +01:00
DeveloperMarius 4cb2fa521f v4-development sync 2021-03-25 13:41:04 +01:00
Simon Sessingø 8835aca02e [FEATURE] Added Request::getContentType for content-type header-parsing
- Added unit-tests for Request::getContentType parsing.
2021-03-25 13:09:23 +01:00
Simon Sessingø 86bb88a41f [FEATURE] Added better support for nested file/arrays in InputHandler.
- Added unit tests for file arrays
- Removed legacy .yml configs
2021-03-25 05:58:49 +01:00
Simon Sessingø fdf11bbc9c Merge pull request #517 from DeveloperMarius/file-tests
PHPUnit file tests
2021-03-25 04:34:29 +01:00
Simon Sessingø 2b9403db28 Features & bugfixes
- Feature: added new getFirstHeader to Request object that will return the first header found from array list- used to simplify the code.
- Feature: added new InputHandler::getValueFromArray method that loops through input-items to ensure that value is always returned.
- Fixed calling getUrl with array as parameters option throws error.
- Fixed `SimpleRouter::getUrl` having wrong nullable return type.
2021-03-25 03:41:11 +01:00
Simon Sessingø 0ec7c0d960 Optimisations
- Added phpunit cache to .gitignore
- Updated README with latest helper.php example.
- Minor phpDocs changes.
2021-03-24 23:18:17 +01:00
Simon Sessingø d9c63699f5 Merge pull request #516 from DeveloperMarius/phpunit-fix
[IMPORTANT] PHPUnit fix: Element 'coverage': This element is not expected.
2021-03-24 14:03:58 +01:00
DeveloperMarius 3e1333ccd4 Defined variables for the file data 2021-03-24 13:32:15 +01:00
DeveloperMarius fef65313e5 Added file content test for all platforms 2021-03-24 13:20:41 +01:00
DeveloperMarius 5624c4b2bb testFiles and test file content on server 2021-03-24 13:11:07 +01:00
DeveloperMarius 543e550daf PHPUnit fix: Element 'coverage': This element is not expected. 2021-03-24 11:48:29 +01:00
Simon Sessingø 22d531178a Merge pull request #512 from DeveloperMarius/github-actions
GitHub actions
2021-03-23 15:57:03 +01:00
DeveloperMarius 0892f5b6f3 replaced php version 7.3 with with 7.1 2021-03-23 15:49:32 +01:00
DeveloperMarius 1d48034910 code cleanup 2021-03-23 15:48:36 +01:00
DeveloperMarius 61fee760b0 use phpunit version 7.5.20 like composer 2021-03-23 15:46:22 +01:00
DeveloperMarius cfc8b5db43 Test PHP Version 7.1 with PHPUnit 7 2021-03-23 15:44:43 +01:00
DeveloperMarius 619a8d00b4 v4-development sync 2021-03-23 15:42:44 +01:00
Simon Sessingø 42633ec453 php-unit updates 2021-03-23 15:38:10 +01:00
Simon Sessingø dbd8d381e7 Updated travis.yml 2021-03-23 15:31:47 +01:00
Simon Sessingø 57936b7857 Reverted back php-unit version 2021-03-23 15:26:57 +01:00
Simon Sessingø c466af556e Updated travis.yml 2021-03-23 15:22:31 +01:00
Simon Sessingø db63aff668 Updated php-unit to version 9 2021-03-23 15:20:05 +01:00
Simon Sessingø df52ec3df7 Updated travis.yml 2021-03-23 15:15:58 +01:00
Simon Sessingø 7920188956 Updated travis.yml 2021-03-23 15:11:44 +01:00
Simon Sessingø 635b127357 Fixed correct return type for InputFile. 2021-03-23 15:06:21 +01:00
Simon Sessingø b9af44299e Merge pull request #514 from skipperbent/v4-inputitem-value
[FEATURE] Add support for mixed value types in InputItem as requested by #438
2021-03-23 15:02:18 +01:00
Simon Sessingø a070af2145 Merge pull request #510 from skipperbent/v4-disable-multi-routing
[FEATURE] Option to disable multi-route-rendering #452
2021-03-23 15:00:14 +01:00
Simon Sessingø a33f2f7e7a Merge branch 'v4-development' into v4-disable-multi-routing 2021-03-23 15:00:01 +01:00
Simon Sessingø 2689486e64 Merge pull request #509 from skipperbent/v4-request-same-routes
[BUGFIX] Issue #439: Fixed multiple request-type on same routes.
2021-03-23 14:57:17 +01:00
Simon Sessingø dfc12d07b0 Merge pull request #508 from skipperbent/v4-bootmanager-fix
[BUGFIX] BootManager findRoute not working.
2021-03-23 14:56:16 +01:00
Simon Sessingø 3ebe1a8af2 Merge pull request #507 from skipperbent/feature-default-namespace
[FEATURE] Default-namespace optimisations #446
2021-03-23 14:53:32 +01:00
Simon Sessingø 680e0256c3 Merge branch 'v4-development' into feature-default-namespace 2021-03-23 14:52:39 +01:00
Simon Sessingø a8068f76a3 Merge pull request #513 from skipperbent/v4-phpdi-remove
[!!!][FEATURE] Removed php-di as suggested by #477
2021-03-23 14:50:19 +01:00
Simon Sessingø 7ba864420e Updated travis.yml 2021-03-23 14:47:11 +01:00
Simon Sessingø 8670af356b Updated travis.yml 2021-03-23 14:37:04 +01:00
Simon Sessingø 6be9d1003c Updated travis.xml 2021-03-23 14:33:25 +01:00
DeveloperMarius 5095b1abc9 cleanup 2021-03-23 13:54:00 +01:00
DeveloperMarius 5275653606 Correct test to succeed 2021-03-23 13:41:27 +01:00
DeveloperMarius 62f5e5cbbd Add Failing test 2021-03-23 13:40:22 +01:00
DeveloperMarius 08008ca847 Add PHP 7.3 check 2021-03-23 13:40:08 +01:00
DeveloperMarius 9f0373938d fix composer test command 2021-03-23 13:35:57 +01:00
DeveloperMarius c408f79d8a changed to PHP >= 3 & run test via composer test 2021-03-23 13:34:55 +01:00
DeveloperMarius 69fdfb3560 remove matrix include & PHPUnit requires PHP >= 7.3 2021-03-23 13:30:57 +01:00
DeveloperMarius a103c71163 Try to fix composer install missing 2021-03-23 13:28:17 +01:00
DeveloperMarius d3000775d6 correct composer exec path 2021-03-23 13:27:18 +01:00
DeveloperMarius 824ee86652 Try to fix composer install missing 2021-03-23 13:25:44 +01:00
DeveloperMarius 495cfba613 Added relative path 2021-03-23 13:22:32 +01:00
DeveloperMarius 5eadb79c64 add phpunit tool 2021-03-23 13:20:29 +01:00
DeveloperMarius 4725b330fe change test command & phpunit file update 2021-03-23 13:19:36 +01:00
DeveloperMarius 537d607b9f Try to fix caching bugg 2021-03-23 13:17:07 +01:00
DeveloperMarius 9029a84fdd reduced platforms and version for test & added composer 2021-03-23 13:04:59 +01:00
DeveloperMarius 87985841de complete change of tests 2021-03-23 12:48:49 +01:00
DeveloperMarius 37228d2bac revert change 2021-03-23 12:47:24 +01:00
DeveloperMarius 26a1659734 Try to fix failures 2021-03-23 12:07:53 +01:00
DeveloperMarius 554d562e56 Seem to get no error local, test on actions 2021-03-23 12:04:10 +01:00
DeveloperMarius 577c87c527 Try to run phpunit from composer 2021-03-23 11:57:03 +01:00
DeveloperMarius d7a295cb5c Change php version and test execution 2021-03-23 11:53:29 +01:00
Marius Karstedt 656946fbb2 Used cmd instead of run 2021-03-23 03:33:51 +01:00
Marius Karstedt aa8211a273 added tests to composer 2021-03-23 03:23:18 +01:00
Simon Sessingø 90b0747dbd [BUGFIX] Add support for mixed value types in InputItem as requested by #438 2021-03-23 01:36:23 +01:00
Simon Sessingø e721a92156 Updated readme 2021-03-23 01:25:57 +01:00
Simon Sessingø 67211e5332 Updated readme + gitignore 2021-03-23 01:24:17 +01:00
Simon Sessingø a44a93d705 [!!!][FEATURE] Removed php-di as suggested by #477
NOTE: Custom class-loader should be used to create custom integrations with frameworks like php-di. See documentation for more information.

- Removed all references to php-cli from composer + code.
- Added ClassLoader php-unit tests.
2021-03-23 00:46:17 +01:00
DeveloperMarius 4adfa4f322 Rollback to warning: 'Suggestion: Migrate your XML configuration using --migrate-configuration!' 2021-03-23 00:18:14 +01:00
DeveloperMarius f9c0c83b70 migrate configuration 2021-03-23 00:09:45 +01:00
DeveloperMarius eebd537749 removed syntaxCheck (throws error) https://stackoverflow.com/questions/44328114/phpunit-what-does-syntaxcheck-configuration-parameter-stands-for-exactly/44331140#44331140 2021-03-22 23:55:11 +01:00
DeveloperMarius 0e58d556f0 Maby have to add args? 2021-03-22 23:45:37 +01:00
DeveloperMarius 791ba3199d Error says phpunit needs PHP >= 7.3, let's try it 2021-03-22 23:38:23 +01:00
DeveloperMarius 3c8740769a No try to add unit tests 2021-03-22 23:36:32 +01:00
DeveloperMarius 029739f241 Just install composer without tests 2021-03-22 23:31:39 +01:00
DeveloperMarius c67c6759a8 Try without unit tests 2021-03-22 23:28:16 +01:00
DeveloperMarius 21710c083c Trying to fix docker build error 2021-03-22 23:19:18 +01:00
DeveloperMarius 7013822358 changed bootstrap file 2021-03-22 23:13:05 +01:00
DeveloperMarius deb6922d0c change php version to 7.1 2021-03-22 23:10:20 +01:00
DeveloperMarius cb2cb91a0a Add on pull request 2021-03-22 23:00:39 +01:00
DeveloperMarius 533dd08217 Github Actions CI on push 2021-03-22 22:58:53 +01:00
DeveloperMarius 5508c73e85 getIp() update to new header method 2021-03-22 22:32:44 +01:00
DeveloperMarius d5dc81e26e v4-development sync & getIp() update to new header method 2021-03-22 22:25:37 +01:00
Simon Sessingø 6686de46b9 Updated link to simple-router demo. 2021-03-22 20:35:42 +01:00
Simon Sessingø 5c8ff17aec Updated documentation for information about multi-route-rendering. 2021-03-22 19:43:13 +01:00
Simon Sessingø 1d2e5f47d9 [FEATURE] Option to disable multi-route rendering
- Added option to disable multi-route rendering by calling `Router::setRenderMultipleRoutes($bool)`.
- Added alias for easier access `SimpleRouter::enableMultiRouteRendering($bool)`.
- Added php-unit tests for multi-routing enabled and disabled.
2021-03-22 19:34:55 +01:00
Simon Sessingø f74252e8cc Removed newline 2021-03-22 18:34:14 +01:00
Simon Sessingø 2fb59854be [BUGFIX] Issue #439: Fixed multiple request-type on same routes. 2021-03-22 18:33:16 +01:00
Simon Sessingø 801f1e68cc [BUGFIX] BootManager findRoute not working.
- Fixed findRoute not working in BootManager as reported by issue: #448
- Added more comprehensive php-unit tests for bootmanagers including findUrl.
2021-03-22 18:05:27 +01:00
Simon Sessingø fa83d2f74b Default-namespace changes.
- Added new ClassNotFoundHttpException thrown when class is not found.
- ClassNotFoundHttpException is now thrown when class/method is not found (backwards compatible).
- Added unit-tests for default-namespace tests (rewrite + append cases).
2021-03-22 17:03:22 +01:00
Simon Sessingø fd585e8b9d [FEATURE] Changed behavior for default-namespace after issue #446
- Router::setDefaultNamespace() no longer has to be set in the beginning of routes.php.
- Default namespace are now set once the router is started (Router::start()).
- [WIP] Added unit-tests for custom-namespaces.
2021-03-22 16:41:20 +01:00
Simon Sessingø 11fffd9a7b [FEATURE] Added option to get/set the filterEmptyParams option on IRoute classes (as requested by: #453). 2021-03-22 15:06:20 +01:00
Simon Sessingø 87e9c19edb Added unit-tests for input->all() method. 2021-03-22 14:49:33 +01:00
Simon Sessingø 8a0f30c05e Custom regex fix
- Fixed issue with custom-regex maching both host-name and url (issue: #503).
- Changed TestRouter so host-name is always set.
2021-03-22 14:09:27 +01:00
Marius Karstedt 1e0417b249 also check remote-addr (can be edited https://stackoverflow.com/questions/5092563/how-to-fake-serverremote-addr-variable) 2021-03-22 11:18:16 +01:00
Marius Karstedt 24f7e3ab13 Validate IP header 2021-03-22 11:15:47 +01:00
Simon Sessingø d2b3ea4f54 Added better header parsing to Request-class.
- Added `tryParse` argument to the `getHeader` method. When enabled the method will try to parse headers from both server and client-side (enabled by default).
- Simplified references that checks for both variants of header (http/non http).
- Simplified getIp method of the Request-class.
2021-03-22 11:14:22 +01:00
Marius Karstedt 90a0ca2ee8 Add cf ip header to none save call 2021-03-22 11:06:33 +01:00
Marius Karstedt 9897f66a25 Add $safe 2021-03-22 11:04:33 +01:00
Marius Karstedt e77b723db3 Add http-client-ip header 2021-03-22 10:57:58 +01:00
Simon Sessingø 0aeefa1cba Removed ob_end_clean when using ob_get_clean. 2021-03-21 15:22:35 +01:00
Simon Sessingø 8254c5b100 Development
- Removed unused class references.
- Removed escape from `-` in reg-ex as it's only required when next to character-class.
- Added `$_FILE` support for `all` method.
- Bugfixes.
2021-03-21 15:19:27 +01:00
Simon Sessingø 4639879a67 Merge pull request #502 from skipperbent/DeveloperMarius-get-csrf-token
Developer marius get csrf token
2021-03-21 14:59:08 +01:00
Simon Sessingø 14fe889298 Merge branch 'v4-development' into DeveloperMarius-get-csrf-token 2021-03-21 14:58:20 +01:00
Simon Sessingø dfcbbb4033 Merge pull request #501 from DeveloperMarius/get-csrf-token
get csrf token in request and test for prefix 'http-' in csrf token header
2021-03-21 14:57:10 +01:00
Simon Sessingø e8a1eac167 Development
- Moved request-types constants from abstract Route class to global Request-class and changed references.
- Changed code to use new global request-type constants.
- Optimized InputHandler class so it only parses inputs once when calling all-method.
- Forced csrf-token post-value are now availible in all requestTypePost methods.
2021-03-21 14:52:34 +01:00
Simon Sessingø 2ff278baef Merge branch 'get-csrf-token' of https://github.com/DeveloperMarius/simple-php-router into DeveloperMarius-get-csrf-token 2021-03-21 14:16:20 +01:00
Simon Sessingø 388be3d870 Merge pull request #500 from DeveloperMarius/remove-idea-config
remove .idea files
2021-03-21 14:03:05 +01:00
Simon Sessingø f45e0bd12a Removed .idea folder and removed "index" output from the unit-tests. 2021-03-21 13:59:35 +01:00
Marius Karstedt 31b4b4673e add csrf token check for patch 2021-03-21 12:20:57 +01:00
Marius Karstedt 05e5461acb get csrf token in request; Test for prefix 'http-' in csrf token header 2021-03-21 11:40:37 +01:00
Marius Karstedt 3970ad85c4 remove .idea files 2021-03-21 10:47:49 +01:00
Simon Sessingø 38ce2e6bba Unit test fixes 2021-03-21 08:02:41 +01:00
Simon Sessingø 2306ab47db Merge pull request #499 from skipperbent/fix-partial-group
[BUGFIX] Fixed issue with child groups not loading when using partialGroups (issue: #456)
2021-03-21 07:57:14 +01:00
Simon Sessingø 4674dbef1a Merge branch 'v4-development' into fix-partial-group 2021-03-21 07:57:01 +01:00
Simon Sessingø f50ed6cd27 Merge pull request #498 from skipperbent/v4-array-callback
[FEATURE] Added support for class hinting on routes as requested by #491
2021-03-21 07:55:27 +01:00
Simon Sessingø d70b153189 [BUGFIX] Fixed issue with child groups not loading when using partialGroups (issue: #456) 2021-03-21 07:39:17 +01:00
Simon Sessingø 21d180ebc9 [FEATURE] Added support for class hinting on routes as requested by #491 2021-03-21 05:55:18 +01:00
Simon Sessingø e78040aabd Fixed existing unit-tests. 2021-03-18 21:58:40 +01:00
Simon Sessingø 252cc4a75d Optimized InputHandler to better support nested values. 2021-03-18 21:58:12 +01:00
Simon Sessingø 24ef438334 Merge pull request #495 from SimonSchobel/patch-1
Fixed bug in getIp
2021-03-18 14:03:39 +01:00
Simon Schøbel 6fc0241bab Fixed bug in getIp
Fixed typo
2021-03-18 08:12:48 +01:00
Simon Sessingø 19b1a14dec Parameters are by default now using regex [\w\-]+ (supports dashes) to avoid confusion. 2021-03-18 03:24:47 +01:00
Simon Sessingø fb726c3613 Issue #437: Fixed CSRF-token returning null on first refresh after cookies are removed. 2021-03-18 02:50:06 +01:00
Simon Sessingø 11a69c2f72 Fixed user-tests after changes to input->all() method. 2021-03-17 20:26:07 +01:00
Simon Sessingø ff8ef9d412 Merge branch 'v4-development' of github.com:skipperbent/simple-php-router into v4-development 2021-03-17 20:20:56 +01:00
Simon Sessingø ca88e86c3d Development
- Removed unused exception from PHP-docs.
- Fixed types not same as declared.
- Fixed issues with reg-ex and php-unit tests.
- Removed unnecessary type casting.
- Declared functions as static (better scoping + performance).
- Moved `\is_callable($callback) === false` as the execution costs less than previous in `Router.php`.
- Changed `ob_get_contents` to `ob_get_clean`.
- Added type hints to methods.
2021-03-17 20:20:28 +01:00
Simon Sessingø 5a24dfd4a1 Merge pull request #461 from b3none/master
Alter behaviour of input()->all when a filter is passed.
2021-03-17 19:25:51 +01:00
Simon Sessingø 891c2092eb Changed codestyle to match the rest 2021-03-17 19:25:18 +01:00
Simon Sessingø 845ef9db69 Merge pull request #450 from RedooNetworks/master
Fix warning, because of wrong calculated cookie expiration timestamp
2021-03-17 19:17:01 +01:00
Simon Sessingø 00c9f9cafd Update composer.json 2021-03-17 19:16:08 +01:00
Simon Sessingø 0fe2733c85 Merge pull request #445 from KarelWintersky/patch-1
Update README.md
2021-03-17 19:14:15 +01:00
Simon Sessingø ce3a2014d1 Merge pull request #449 from jatubio/patch-4
Fix 'must be an instance of Closure, array given' error when $closure is a object method.
2021-03-17 19:12:00 +01:00
Simon Sessingø 27cd8b8a1f Added support for objects like array etc as default-value. Value is now less strict. 2021-03-17 15:43:00 +01:00
Alex Blackham 93c0622b9d Altered variable name in foreach to be less ambiguous. 2019-06-21 10:45:57 +01:00
Alex Blackham 572ba1695b The input()->all method will now set every key specified in the filter. If the key doesn't exist it will be set to null. 2019-06-21 10:44:31 +01:00
Stefan Warnat 1c5701a297 Update compsoer.json 2019-05-19 11:02:09 +02:00
Stefan Warnat 11df7ca18c Fix warning, because of wrong calculated cookie expiration timestamp 2019-02-09 11:04:07 +01:00
Juan Antonio Tubio b21feca1fc Update IClassLoader.php
Fix 'must be an instance of Closure, array given' error when $closure is a object method.
2019-02-08 21:40:04 +01:00
Juan Antonio Tubio 2a3238f30a Update ClassLoader.php
Fix 'must be an instance of Closure, array given' error when $closure is a object method.
2019-02-08 21:36:58 +01:00
Karel Wintersky cb141314f7 Update README.md
fix https://github.com/skipperbent/simple-php-router/issues/444
2019-01-28 17:43:11 +03:00
Simon Sessingø 153f8630f2 Merge pull request #435 from skipperbent/v4-release
V4 release
2018-11-25 00:47:26 +01:00
Simon Sessingø b715c48415 Merge pull request #434 from skipperbent/v4-development
Version 4.2.0.6
2018-11-25 00:47:02 +01:00
Simon Sessingø d601e8eca3 Bugfixes and optimisations.
- Fix for __invoke methods (issue: #429)
- Fixed not being able to parse body of PUT request.
- BaseCsrfVerifier expects the field name to be "csrf-token" (issue: #432)
- Minor optimisations
2018-11-25 00:44:20 +01:00
Simon Sessingø 4a8b71a0b5 Merge pull request #430 from Tahrz/master
fix for __invoke methods (if there are only in class (Action))
2018-11-25 00:27:54 +01:00
Simon Sessingø bd01a8a802 Merge pull request #431 from matheusheiden/master
Set HTTP method before initializing InputHandler
2018-11-25 00:27:30 +01:00
Matheus 55bcd4e030 Merge pull request #1 from matheusheiden/matheusheiden-patch-1
Set HTTP method before initializing InputHandler
2018-11-10 22:17:08 -02:00
Matheus d4a6d504b1 Set HTTP method before initializing InputHandler
This is needed because without it the InputHandler doesn't know which http method is being used, and because of that it can't parse the body of a PUT request
2018-11-10 22:16:45 -02:00
Taras Victorovich 9e3b1b6baa fix for __invoke methods (if there are only in class (Action)) 2018-10-02 15:40:26 +03:00
Simon Sessingø 1395527cc6 Merge pull request #428 from skipperbent/v4-release
V4 release
2018-08-31 01:41:48 +02:00
Simon Sessingø ac594ebde2 Merge pull request #427 from skipperbent/v4-development
Version 4.2.0.5
2018-08-31 01:41:36 +02:00
Simon Sessingø 064154f27f Merge pull request #426 from skipperbent/v4-optimisations
Optimizations
2018-08-31 01:41:05 +02:00
Simon Sessingø 0ac7fd559a Optimizations
- Settings parameter in group method are no longer optional.
- Updated README to contain PHP JSON-extension under requirements.
- Updated composer.json to include php json extension.
2018-08-31 01:40:21 +02:00
Simon Sessingø da8dfdb135 Merge pull request #424 from skipperbent/v4-release
V4 release
2018-08-24 17:24:48 +02:00
Simon Sessingø 57c9da2c42 Merge pull request #423 from skipperbent/v4-development
Version 4.2.0.4
2018-08-24 17:24:25 +02:00
Simon Sessingø 6bdfe06223 Merge pull request #422 from skipperbent/v4-optional-character-fix
Fix for issue #421: Incorrectly optional character in route
2018-08-24 17:23:43 +02:00
Simon Sessingø 5c2a973214 Merge branch 'v4-development' into v4-optional-character-fix 2018-08-24 17:23:33 +02:00
Simon Sessingø a7cbacbde7 Fix for issue #421: Incorrectly optional character in route 2018-08-24 17:16:02 +02:00
Simon Sessingø f0c851e49d Merge pull request #417 from skipperbent/v4-release
V4 release
2018-04-22 05:37:41 +02:00
Simon Sessingø 45a5176b3c Merge pull request #416 from skipperbent/v4-development
Version 4.2.0.3
2018-04-22 05:37:29 +02:00
Simon Sessingø 2d042391aa Merge pull request #415 from skipperbent/v4-fix-input-all-empty
Fixed returning empty array when positing json (issue #413)
2018-04-22 05:36:10 +02:00
Simon Sessingo 4d842d0583 Fixed returning empty array when positing json (issue #413) 2018-04-22 05:35:15 +02:00
Simon Sessingø 7c54d319e6 Merge pull request #411 from skipperbent/v4-release
V4 release
2018-04-06 19:46:01 +02:00
Simon Sessingø 9ccff91287 Merge pull request #410 from skipperbent/v4-development
Version 4.2.0.2
2018-04-06 19:45:51 +02:00
Simon Sessingø 8653bfa86f Development
- Fixed 403 not allowed exception is now thrown as NotFoundHttpException.
- Added REQUEST_TYPE_HEAD to Route class.
- Minor optimizations.
2018-04-06 19:44:30 +02:00
Simon Sessingø e51290dfd8 Merge pull request #409 from skipperbent/v4-release
V4 release
2018-04-06 18:04:28 +02:00
Simon Sessingø 13501b3f88 Merge pull request #408 from skipperbent/v4-development
Version 4.2.0.1
2018-04-06 18:04:01 +02:00
Simon Sessingø b8cfc4eb0b Fixed default parameter for all method allowed to by empty. 2018-04-06 18:01:49 +02:00
Simon Sessingø 5db4621831 Added methods for adding get, post and file parameters manually. 2018-04-06 18:00:59 +02:00
Simon Sessingø d4bbb6641d Merge pull request #407 from skipperbent/v4-release
V4 release
2018-04-06 17:28:12 +02:00
Simon Sessingø af641e3805 Merge pull request #406 from skipperbent/v4-development
Version 4.2.0.0
2018-04-06 17:27:42 +02:00
Simon Sessingø d38f81836d Development
- Added new Redirect method to SimpleRouter class.
- Changed method-names in InputHandler for better description.
- Fixed return-types for InputHandler for collections.
- Added unit-tests for InputHandler (get, post).
- Optimisations.
2018-04-06 17:20:00 +02:00
Simon Sessingø ca1e2ef94b Merge pull request #404 from skipperbent/v4-release
V4 release
2018-04-05 12:19:27 +02:00
Simon Sessingø 89be00a72a Merge pull request #402 from skipperbent/v4-development
Version 4.1.0.0
2018-04-02 14:56:12 +02:00
Simon Sessingø 30a2ddeed9 Development
- Added new event when adding route.
- Added `prependUrl` method to `LoadableRoute` class.
- Added unit-test for add-route event.
- Updated documentation to reflect new changes.
2018-04-02 14:53:36 +02:00
Simon Sessingø 313833d78a Merge pull request #400 from skipperbent/v4-development
Fixed `exists` method in `InputHandler` returning incorrect value.
2018-04-01 04:28:06 +02:00
Simon Sessingø 17a7b28e82 Fixed exists method in InputHandler returning incorrect value. 2018-04-01 04:26:48 +02:00
Simon Sessingø e77d78e2f2 Merge pull request #399 from skipperbent/v4-development
Bugfixes and optimizations
2018-04-01 03:02:49 +02:00
Simon Sessingø 1dc88d23e1 Bugfixes and optimizations
- Fixed `hasParam` not working returning expected value in `Url` class.
- Chained the remaining methods in the `Url` class.
- Simplified `removeParam` method in `Url` class.
- Added new `removeParams` method to `Url` class for removal of multiple params.
2018-04-01 03:02:21 +02:00
Simon Sessingø bd033d9e13 Merge pull request #398 from skipperbent/v4-development
Version 4.0.0.11
2018-03-30 06:48:15 +02:00
Simon Sessingø 53f0b7d8e2 - Fixed getName method in LoadableRoute class can contain nullable value. 2018-03-30 06:47:27 +02:00
Simon Sessingø 5df0c12864 Updated helpers 2018-03-30 05:54:12 +02:00
Simon Sessingø 5bae3ff773 Merge pull request #397 from skipperbent/v4-development
Version 4.0.0.10
2018-03-30 05:38:27 +02:00
Simon Sessingø 833961ddc3 Fixed getError in InputFile returning string instead of int. 2018-03-30 05:37:04 +02:00
Simon Sessingø 36388f0f79 Merge pull request #396 from skipperbent/v4-development
Version 4.0.0.9
2018-03-30 05:23:54 +02:00
Simon Sessingø ce63e247b1 Bugfixes
- Fixed `Url` not outputting correct class when used in json_encode.
- Fixed `IInputItem` being too strict about strings which may be nullable.
2018-03-30 05:22:41 +02:00
Simon Sessingø d2d3938bf4 Merge pull request #395 from skipperbent/v4-development
Bugfixes
2018-03-29 23:48:32 +02:00
Simon Sessingø 80a42030ea Bugfixes
- Fixed getting specific input-value by request-method in InputHandler.
- Added .idea files
2018-03-29 23:47:27 +02:00
Simon Sessingø 1d6a2fafff Merge pull request #394 from skipperbent/v4-development
Removed namespace from groups
2018-03-29 23:28:47 +02:00
Simon Sessingø 17471a53cd Removed namespace from groups 2018-03-29 23:28:03 +02:00
Simon Sessingø e97e624cef Merge pull request #393 from skipperbent/v4-development
Bugfixes
2018-03-29 23:14:22 +02:00
Simon Sessingø da219d0b19 Bugfixes
- Fixed rewrite from ExceptionHandler sometimes not working correctly.
- Fixed default-namespace for Group and partial groups.
2018-03-29 23:13:56 +02:00
Simon Sessingø 30d2285699 Merge pull request #392 from skipperbent/v4-development
Bugfixes
2018-03-29 22:36:01 +02:00
Simon Sessingø a1dc4c5119 Bugfixes
- Updated `input` helper function.
- Update documentation to reflect v4 changes in `InputHandler` class.
2018-03-29 22:35:21 +02:00
Simon Sessingø 87e1fa3775 Merge pull request #391 from skipperbent/v4-development
Bugfixes
2018-03-29 22:18:26 +02:00
Simon Sessingø a11595fb86 Bugfixes
- Fixed `$methods` argument not properly passed in `InputHandler` class.
- Updated helpers.php with latest changes.
2018-03-29 22:17:53 +02:00
Simon Sessingø 555afd04f1 Merge pull request #390 from skipperbent/v4-development
Fixed defaultValue issue in `getValue` method in `InputHandler` class.
2018-03-29 21:45:26 +02:00
Simon Sessingø e6db83c97a Fixed defaultValue issue in getValue method in InputHandler class. 2018-03-29 21:44:41 +02:00
Simon Sessingø 32e5dd623c Merge pull request #389 from skipperbent/v4-development
Version 4.0.0.1
2018-03-29 21:26:37 +02:00
Simon Sessingø 8eded4a619 Updated documentation. 2018-03-29 21:19:52 +02:00
Simon Sessingø af2ac6031d Development
- Added dependency injection support.
- Added php-di composer dependency.
- Added `ClassLoader` class.
- Added `IClassLoader` interface.
- Added unit-tests for dependency injection.
- Updated documentation to reflect new features.
2018-03-29 21:16:02 +02:00
Simon Sessingø cca2f5cb88 Added donate option 2018-03-29 19:13:55 +02:00
Simon Sessingø 1a59a659fe Updated documentation 2018-03-29 19:02:31 +02:00
Simon Sessingø 4c61899560 Updated documentation 2018-03-29 19:00:18 +02:00
Simon Sessingø 931b50098c Updated documentation 2018-03-29 18:54:03 +02:00
Simon Sessingø f5a023117a Development
- Added event-arguments data.
- Added event-arguments to the event list in documentation.
- Fixed missing exceptions thrown in phpDocs.
- Added unit-tests for new event functionality.
2018-03-29 18:51:28 +02:00
Simon Sessingø a9c03f9271 Development
- Updated `helpers.php` and helpers example in documentation.
- MalformedUrlException is now handled properly by Router to avoid phpStorm syntax highlights in routes.
- Added `getUrlCopy` to `Request` class, used to clone the current route (to keep domain etc.)
- `setUrl` in `Request` are now strict and requires `Url` object and no longer accepts strings.
- Renamed `hasRewrite` property to `hasPendingRewrite` in `Request` class.
- Renamed `hasRewrite` and `setHasRewrite` methods to `hasPendingRewrite` and `setHasPendingRewrite` in `Request` class.
- Added better usage of `Url` class. When calling `url` you can now use the methods on the `Url` class to filter params, get relative/absolute url etc. See documentation for more info.
- Renamed `get` method to `getValue` in `InputHandler` class.
- Renamed `getObject` to `get` and removed `$defaultValue` argument in `InputHandler` class.
- Optimized `InputHandler` class.
- Fixed issue with `$token` not being proper string in `BaseCsrfVerifier` when token is not found.
- Added php.ini configuration settings to `setcookie` in `CookieTokenProvider` for improved security.
- Added `$router` parameter to `boot` method in `IRouterBootManager` which allows for further manipulation of the router within the bootmanager.
- Renamed `$processingRoute` property to `$isProcessingRoute` in `Router` class.
- Fixed `reset` method not resetting CSRF-verifier in `Router` class.
- Moved `arrayToParams` helper-method from `Router` to `Url` class.
- Began to add Event-functionality to router.
- Added `addEventHandler` method to `SimpleRouter` class.
- Moved `Pecee\SimpleRouter\Handler\CallbackExceptionHandler` to `Pecee\SimpleRouter\Handlers\CallbackExceptionHandler`.
- Moved `Pecee\SimpleRouter\Handler\IExceptionHandler` to `Pecee\SimpleRouter\Handlers\IExceptionHandler`.
- Added Events section to documentation.
- Added more information on url-handling in documentation.
- Optimisations.
2018-03-29 18:17:42 +02:00
Simon Sessingø aa56d45f9c Updated documentation 2018-03-29 10:13:23 +02:00
Simon Sessingø 9b175d5794 Merge pull request #388 from skipperbent/v4-development
Version 4.0.0.0
2018-03-27 01:13:25 +02:00
Simon Sessingø ef4582dbe0 Fixed wrong return-type in loadClass. 2018-03-27 01:11:44 +02:00
Simon Sessingø 54ae830ec8 Merge pull request #386 from skipperbent/v4-php7-port
Version 4
2018-03-26 23:57:54 +02:00
Simon Sessingø 085f98cf08 Development
- Better php7 support.
- Added easier way to debug router.
- Improvements and bugfixes.
- Updated documentation.
2018-03-26 23:43:27 +02:00
Simon Sessingø f23d569757 Added support for PHP7 2018-03-20 03:38:55 +01: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 Sessingo d279d5598d Development
- Fixed issue with subdomains on groups.
- Fixed issue with loadble routes sometimes mistakenly inheriting wrong group.
- Routes will now check if group matches before matching any custom regex.
- Added setValue method to IInputItem class to streamline the InputFile and InputItem classes.
- Other minor optimisations.
- Documentation: fixed broken links and cleaned it up.
2018-03-03 23:37:14 +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 Sessingo be39010be3 Throw correct exception-types. 2018-02-27 08:58: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 Sessingo 79c82c90cc Fixed issue with PDO exception not returning correct type for error-code. 2018-02-27 08:20:25 +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 Sessingo 6d7d07669b Optimisations 2018-02-27 00:19:44 +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 Sessingo 98bf95bfc9 Added back getPath. 2018-02-27 00:14:01 +01:00
Simon Sessingo b051bcf02b Stop router from processing routes if no valid route is found. 2018-02-27 00:12:45 +01:00
Simon Sessingo b8d5106f4e Removed getPath from url. 2018-02-27 00:00:54 +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 Sessingo cfc9ac138a Fixed setUrl issue. 2018-02-26 23:48:09 +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 Sessingo a25be983b8 Development
- Renamed Uri class to Url.
- Renamed setUri and getUri to setUrl and getUrl.
- Added custom Exceptions and ensured that router only throws HttpExceptions.
- Added isAjax method to Request class.
- Added better phpDocs.
- Other minor optimisations.
2018-02-26 23:21:26 +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ø a0c5bbdcc0 Merge pull request #367 from skipperbent/v3-rewrite-update
Simplified url-rewriting and callback handling.
2018-02-24 05:36:37 +01:00
Simon Sessingo 50c6499efb Simplified url-rewriting and callback handling. 2018-02-24 05:35:05 +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 Sessingo 55a96a441e Set default value to 0 to triggering error when calling getErrors on empty object. 2018-02-19 23:04:26 +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 Sessingo 6b8c823427 Fixed: refresh now returns correct original url (params, querystrings etc). 2018-02-19 16:34:22 +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ø 932dfbf2b7 Merge pull request #357 from skipperbent/v3-optimisations
Optimisations
2018-02-14 12:38:07 +01:00
Simon Sessingo 8d87aab35b Optimisations 2018-02-05 05:27:47 +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 Sessingo fc2e2e1e82 Fixed Input::all when php://input is not availible 2018-01-14 15:53:44 +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ø 71518431a9 Merge pull request #348 from skipperbent/v3-fix-default-value
Fixed parameter default-value being overwritten (issue: #347).
2018-01-06 03:31:03 +01:00
Simon Sessingø cec240ab0c Merge pull request #349 from skipperbent/feature-get-processed-routes
Added getProcessedRoutes method to Router class.
2018-01-06 03:30:55 +01:00
Simon Sessingo 4d2b584936 Fixed parameter default-value being overwritten (issue: #347). 2018-01-06 03:27:27 +01:00
Simon Sessingo a102c70700 Added getProcessedRoutes method to Router class. 2018-01-06 01:23:00 +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 Sessingo f2d106c649 _method is not supported as fallback for every request-type. 2017-12-17 10:37:33 +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 Sessingo 72d33dd497 Compatibility: setUri can be both string and Uri object. 2017-12-17 09:51:56 +01:00
Simon Sessingo e23dd37435 Reverted changes 2017-12-16 23:29:00 +01:00
Simon Sessingo aa5ec47051 Removed key check in __get as it's already performed in the __isset method. 2017-12-16 23:24:31 +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 Sessingo 155729074b Fixed filtering on get-params in all method. 2017-12-16 12:48:10 +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ø 0be7bfcfd9 Fixed utf-8 header issue (issue: #333) 2017-12-09 23:56:37 +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ø 7f8d90eef8 Added $options and $debt param to json method in Response class. 2017-12-06 19:04:48 +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ø 4bb784bcec Merge pull request #329 from skipperbent/v3-optimisations
Optimisations
2017-12-02 20:30:02 +01:00
Simon Sessingo c4ee1b9186 Optimisations 2017-12-02 19:30:30 +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ø efd5159604 Bugfixes
- Fixed issue with parsing parameters on some occasion.
- Optimisations.
2017-11-27 02:03:12 +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ø bdfc36ed5c More Input-related optimisations. 2017-11-27 00:00:19 +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ø d921ae8105 Bugfixes
- Fixed minor issues with File Input-parsing.
- Optimized Input class.
2017-11-26 23:42:28 +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ø af2be14ccb Fixed url parsing issues 2017-11-26 18:45:06 +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ø fae2e84c98 Fixed typo 2017-11-26 18:29:00 +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ø c90c74b88f Merge pull request #317 from skipperbent/v3-optimisations
V3 optimisations
2017-11-26 18:06:37 +01:00
Simon Sessingø 05f2493304 Development
- Optimized Uri class.
- Request class now uses SimpleRouter to get default-namespace as was
originally the intended behavior.
- Documentation changes.
2017-11-26 18:03:14 +01:00
Simon Sessingø 0856caa9de Merge branch 'v3-development' of github.com:skipperbent/simple-php-router into v3-optimisations 2017-11-26 17:38:49 +01:00
Simon Sessingø 35dc26d741 Optimisations
- Fixed issue with `InputFile` not setting file-name properly.
- Fixed issue with `InputFile` not setting the correct index when
posting certain arrays.
- Made Csrf-token cookie provider more versitile by creating new
`CookieTokenProvider` and `ITokenProvider` classes.
- Strict-checks optimisations.
- Updated documentation to reflect new changes.
2017-11-26 17:32:33 +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ø f2819f866e Merge pull request #314 from skipperbent/v3-documentation-spelling
Fixed spelling in documentation
2017-11-25 01:34:01 +01:00
Simon Sessingø b9aa348b38 Merge branch 'v3-development' of github.com:skipperbent/simple-php-router into v3-development 2017-11-25 01:32:24 +01:00
Simon Sessingø c29c52ae16 Fixed default-namespace causing router to break on closures. 2017-11-25 01:31:43 +01:00
Simon Sessingo 6547c07113 Fixed spelling in documentation 2017-11-20 21:53:41 +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ø fde77969c0 Merge pull request #310 from skipperbent/v3-csrftoken-update
Csrf-token are now refreshed on each page-load to avoid timeout.
2017-11-10 13:02:23 +01:00
Simon Sessingø c3072e8886 Csrf-token are now refreshed on each page-load to avoid timeout. 2017-11-10 12:59:59 +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ø 97753f5370 Minor optimisations. 2017-11-10 08:23:15 +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
Simon Sessingø 927f8d7b3c Merge pull request #304 from jatubio/patch-3
Documentation: Add IIS Trooubleshooting section.
2017-11-08 04:09:14 +01:00
Juan Antonio Tubio 74177a2082 Update README.md 2017-11-04 00:59:52 +01:00
Juan Antonio Tubio 2221bced4f Update README.md 2017-11-04 00:27:13 +01:00
Juan Antonio Tubio 6559278511 Documentation: Add file exception rules samples to IIS web.config 2017-11-04 00:19:51 +01:00
Simon Sessingø 8b9698229d Merge pull request #302 from skipperbent/v3
V3
2017-10-23 22:08:23 +02:00
Simon Sessingø a565f66c4c Merge pull request #301 from skipperbent/v3-development
Version 3.4.5.3
2017-10-23 22:08:09 +02:00
Simon Sessingø 832ef992a3 Merge pull request #300 from skipperbent/csrf-documentation
Added CSRF form-example in documentation (issue: #299)
2017-10-23 22:05:41 +02:00
Simon Sessingø cc5e417db9 Update README.md 2017-10-23 22:05:27 +02:00
Simon Sessingø 2cc90e28d0 Update README.md 2017-10-23 22:04:28 +02:00
Simon Sessingø eb63a5d6ba Added CSRF form-example in documentation (issue: #299) 2017-10-23 22:01:19 +02:00
Simon Sessingø a07b30a80d Merge pull request #298 from jatubio/patch-2
Added more info on route or method not allowed exception
2017-10-12 15:53:03 +02:00
Juan Antonio Tubio c45cd6347a Added more info on route or method not allowed exception 2017-10-11 16:02:26 +02:00
Simon Sessingø 4a353efc97 Merge pull request #296 from skipperbent/v3
V3
2017-10-07 17:53:27 +02:00
Simon Sessingø f7ce440c56 Merge pull request #295 from skipperbent/v3-development
Fixed: try next exception-handler if one throws error.
2017-10-07 17:53:17 +02:00
Simon Sessingø 41705f030a Fixed: try next exception-handler if one throws error. 2017-10-07 12:33:24 +01:00
Simon Sessingø 18fa0f9610 Merge pull request #294 from skipperbent/v3
V3
2017-09-25 08:51:13 +02:00
Simon Sessingø 66ecf0ee33 Merge pull request #293 from skipperbent/v3-development
Readded csrf_token helper function - don't run away this time.
2017-09-25 08:51:02 +02:00
Simon Sessingø 4ba15033d9 Readded csrf_token helper function - don't run away this time. 2017-09-09 01:40:44 +01:00
Simon Sessingø 60393a3722 Merge pull request #291 from skipperbent/v3
V3
2017-09-03 19:43:22 +02:00
Simon Sessingø cfa18e520a Merge pull request #290 from skipperbent/v3-development
Version 3.4.5
2017-09-03 19:43:09 +02:00
Simon Sessingø 5e448f0835 Merge pull request #289 from skipperbent/v3-group-parameters
V3 partial group support
2017-09-03 19:37:51 +02:00
Simon Sessingø 98ad310404 Updated documentation. 2017-09-03 18:36:41 +01:00
Simon Sessingø a5aac57ce9 Added RoutePartialGroup support. 2017-09-03 18:24:51 +01:00
Simon Sessingø 7f924c7d0a Added RouterGroupTest unit-tests. 2017-09-03 16:49:15 +01:00
Simon Sessingø 3a90578351 Added parameter support for Group routes. 2017-09-03 16:37:20 +01:00
Simon Sessingø 52e0f5ef94 Removed link from documentation that has been moved. 2017-09-01 18:01:56 +01:00
Simon Sessingø 3df3ef36ef Merge pull request #288 from skipperbent/v3
V3
2017-08-31 13:05:45 +02:00
Simon Sessingø 0090c167bb Merge pull request #287 from skipperbent/v3-development
Response->json now accepts either array or \JsonSerializable (issue: #284)
2017-08-31 13:05:14 +02:00
Simon Sessingø ae68598024 Response->json now accepts either array or \JsonSerializable (issue: 2017-08-31 12:03:17 +01:00
Simon Sessingø c723ca7e61 Merge pull request #283 from skipperbent/v3
V3
2017-08-24 16:53:29 +02:00
Simon Sessingø 7362b748af Merge pull request #282 from skipperbent/v3-development
V3 development
2017-08-24 16:53:20 +02:00
Simon Sessingø 4fe85d6568 Merge pull request #281 from skipperbent/v3-fix-urldecode
Urldecode is now used on all urls in Uri class (issue: #268)
2017-08-24 16:53:04 +02:00
Simon Sessingø c82d91375c Urldecode is now used on all urls in Uri class (issue: #268) 2017-08-24 15:50:13 +01:00
Simon Sessingø e3b6899375 Merge pull request #280 from skipperbent/v3
V3
2017-08-24 03:13:06 +02:00
Simon Sessingø e774122b1e Merge pull request #279 from skipperbent/v3-development
V3 development
2017-08-24 03:12:57 +02:00
Simon Sessingø e8aceb291c Merge pull request #278 from skipperbent/v3-fix-iis-encoding
Ensured that Request class is using unencoded-url header to avoid encoding issues on IIS (issue: #268)
2017-08-24 03:12:48 +02:00
Simon Sessingø 957a382248 Ensured that Request class is using unencoded-url header to avoid encoding issues on IIS (issue: #268) 2017-08-24 02:05:18 +01:00
Simon Sessingø a179450018 Merge pull request #277 from skipperbent/v3
V3
2017-08-23 23:50:26 +02:00
Simon Sessingø 48e2e3f9bc Merge pull request #276 from skipperbent/v3-development
V3 development
2017-08-23 23:50:18 +02:00
Simon Sessingø f95e12c49c Merge pull request #275 from skipperbent/fix-unicode
Added unicode parameter regex support.
2017-08-23 23:50:04 +02:00
Simon Sessingø 9f509ac818 Added unicode parameter regex support. 2017-08-23 22:49:35 +01:00
Simon Sessingø ac3e9ed2ac Merge pull request #274 from skipperbent/v3
V3
2017-08-23 22:31:00 +02:00
Simon Sessingø 3b4d23e9ae Merge pull request #273 from skipperbent/v3-development
Version 3.4
2017-08-23 22:30:41 +02:00
Simon Sessingø b3e99a2283 Merge pull request #267 from skipperbent/fix-url
Fixed: return / if all parameters are empty in getUrl method.
2017-08-23 22:17:15 +02:00
Simon Sessingø c1aa5782a1 Merge pull request #271 from skipperbent/v3-feature-uri
Added Uri class which can be used to parse urls.
2017-08-23 22:17:04 +02:00
Simon Sessingø 387d9710d8 Merge pull request #272 from jatubio/patch-1
Add IIS web.config file example
2017-08-23 22:16:53 +02:00
Simon Sessingø fd098266f7 Merge pull request #266 from zaingithub/patch-1
Update README.md
2017-08-23 22:16:24 +02:00
Simon Sessingø c7473938c5 Changed xxx to web.config 2017-08-23 22:09:55 +02:00
Simon Sessingø 65c811356d Fixed url parsing for unicode characters. 2017-08-23 21:04:11 +01:00
Simon Sessingø ea255baec3 Merge branch 'v3-fix-url-encoding' into v3-feature-uri 2017-08-23 20:44:57 +01:00
Simon Sessingø acf37c4023 Updated Uri class. 2017-08-23 20:41:49 +01:00
Juan Antonio Tubio 6fb92d7cae Add IIS web.config file example 2017-08-23 17:28:14 +02:00
Simon Sessingø e84c8c2f02 Uri is now always urldecoded in Request class to ensure compability with
most webservers (issue: #268).
2017-08-23 16:13:44 +01:00
Simon Sessingø 7c970c442c Added Uri class which can be used to parse urls. 2017-08-23 16:10:15 +01:00
Simon Sessingø 4cd1e8e069 Fixed: return / if all parameters are empty in $router->getUrl method. 2017-08-22 17:18:48 +01:00
zaingithub 8720e732e1 Update README.md
Update section : ## Handling 404, 403 and other errors

$exception->getCode to $exception->getCode()
2017-08-20 12:47:27 +07:00
Simon Sessingø 7e8cb91f68 Updated documentation 2017-08-19 16:21:55 +01:00
Simon Sessingø bc67038e11 Added helpers.php file
- Added updated helpers.php file
- Updated helpers section in documentation.
2017-08-19 13:48:47 +01:00
Simon Sessingø e31d8af2f7 Merge pull request #264 from skipperbent/v3
V3
2017-08-18 20:32:33 +02:00
Simon Sessingø 9f285fd0ce Merge pull request #263 from skipperbent/v3-development
V3 development
2017-08-18 20:32:21 +02:00
Simon Sessingø b7f5d31544 Fixed double / when calling findUrl on routes with the first
parameter optional.
2017-08-17 23:31:27 +01:00
Simon Sessingø 5086347802 Bugfix: fixed using empty value in url still parsing current
parameters.
2017-08-13 18:01:48 +01:00
Simon Sessingø 26f89213a2 Merge pull request #260 from skipperbent/v3
V3
2017-08-13 04:14:17 +02:00
Simon Sessingø bb7991afdc Merge pull request #259 from skipperbent/v3-development
Version 3.3.5
2017-08-13 04:14:04 +02:00
Simon Sessingø 796d01bc25 Merge pull request #258 from skipperbent/v3-fixes
Fixes and development
2017-08-13 04:13:40 +02:00
Simon Sessingø 74187ee326 Fixes and development
- `$parameter` argument in `findUrl` method now supports both string,
array and null value to avoid confusion.

- Using return value in callbacks now displays value (issue: #257)
2017-08-13 03:06:27 +01:00
Simon Sessingø f090d6c038 Merge pull request #256 from skipperbent/v3
V3
2017-08-07 22:23:03 +02:00
Simon Sessingø 6e859e11ab Merge pull request #255 from skipperbent/v3-development
V3 development
2017-08-07 22:22:34 +02:00
Simon Sessingø 6aa38cfa4c Merge pull request #254 from skipperbent/v3-url-match-fix
Fixed wrong url matching on some paths (issue: #253 - thanks @alejoloe007jb)
2017-08-07 22:20:32 +02:00
Simon Sessingø 62f0075cf3 Fixed wrong url matching on some paths (issue: #253 - thanks @alejoloe007jb) 2017-08-07 22:18:36 +02:00
Simon Sessingø 6890b60737 Merge pull request #252 from skipperbent/v3
V3
2017-08-01 21:30:52 +02:00
Simon Sessingø a40f81d5fc Merge pull request #251 from skipperbent/v3-development
V3 development
2017-08-01 21:30:42 +02:00
Simon Sessingø a5527a0e8c Merge pull request #250 from skipperbent/bugfix-urlmatch
Bugfix urlmatch
2017-08-01 21:30:13 +02:00
Simon Sessingø 3c73de866e Added code-comments. 2017-08-01 21:25:44 +02:00
Simon Sessingø 883d8a6b0e Fixes for issue #248 2017-08-01 21:09:18 +02:00
Simon Sessingø 3a7b27796a Url matching fixes 2017-08-01 18:29:47 +02:00
Simon Sessingø 1e07140865 Merge pull request #247 from skipperbent/v3
V3
2017-07-18 11:28:33 +02:00
Simon Sessingø 4cca5186f3 Merge pull request #246 from skipperbent/v3-development
V3 development
2017-07-18 11:28:19 +02:00
Simon Sessingø 5437420175 Merge pull request #245 from skipperbent/php5-5-compatibility
Fixed PHP 5.5 compatibility (issue: #244 - thanks @thexeos)
2017-07-18 11:27:59 +02:00
Simon Sessingø 410fa3c9f5 Fixed PHP 5.5 compatibility (issue: #244 - thanks @thexeos) 2017-07-18 11:26:45 +02:00
Simon Sessingø be20fe4dd1 Updated composer.json tags. 2017-07-08 12:20:49 +02:00
Simon Sessingø bc03490100 Merge pull request #243 from skipperbent/v3
V3
2017-07-08 12:03:42 +02:00
Simon Sessingø 0947f6746e Merge pull request #242 from skipperbent/v3-development
V3 development
2017-07-08 12:03:05 +02:00
Simon Sessingø 31c8710ce7 Merge pull request #241 from skipperbent/v-3-3-1
Version 3.3.1
2017-07-08 12:02:41 +02:00
Simon Sessingø 1ac7761d35 Development
- Optimized `.gitignore`.
- Renamed `XSRF-TOKEN` constant to `CSRF-TOKEN` in `CsrfToken`.
2017-07-08 11:56:57 +02:00
Simon Sessingø 9eb7c5c13c Removed 'name' as it's set as the default value when calling the $getItem function. 2017-07-08 11:49:05 +02:00
Simon Sessingø 5151461a02 Added setMiddleware deprecated method in Route class. 2017-07-08 11:42:27 +02:00
Simon Sessingø b07348a3df Development
- Removed temporary `RouterException` class.
- Added object-types to parameters in `CallbackExceptionHandler` and `SimpleRouter` classes.
- Router now renders groups even if callback is null.
- Renamed `setMiddleware` to `addMiddleware` in `Route` class and `IRoute` interface.
- `addMiddleware` now accept both object and class strings in `Route` class.
- `addExceptionHandler` now accept both object and class strings in `RouteGroup` class.
- Added unit-test for rewrite-exception message change: `testRewriteExceptionMessage` in `RouterRewriteTest`.
- Fixed typo: renamed `testSimularUrls` to `testSimilarUrls` in `RouterUrlTest`.
2017-07-08 11:21:18 +02:00
Simon Sessingø d411b31cc2 Changed message-text thrown in NotFoundHttpException to show redirected routes (issue: #240) 2017-07-06 15:46:09 +02:00
Simon Sessingø ca381d445f Development
- Changed router so it supports both string and object as exception handlers.
- Added `Router::error($callback)` method to `Router` class (issue #238).
- Fixed issues calling `getController` and `getMethod` when callback is an object (issue #239).
- Updated documentation to reflect new changes.
- Added `addExceptionHandler` to `IGroupRoute` interface and `RouteGroup` class.
- Other minor bugfixes and optimisations.
2017-07-06 15:25:04 +02:00
Simon Sessingø 2c61cef7ad Merge pull request #237 from skipperbent/v3
V3
2017-05-09 07:06:33 +02:00
Simon Sessingø 98ee60859f Merge pull request #236 from skipperbent/v3-development
Development
2017-05-09 07:06:18 +02:00
Simon Sessingø 9dd80dd1d9 Development
- Fixed global regex match not working properly.
- Feature: added option to change regular expression used for parameters on routes.
- Added unit-tests for custom parameter regular expression.
- Updated documentation to reflect new features.
2017-05-09 06:43:26 +02:00
Simon Sessingø 3f9ab73f7f Merge pull request #235 from skipperbent/v3
V3
2017-05-09 02:52:06 +02:00
Simon Sessingø fef997422e Merge pull request #234 from skipperbent/v3-development
V3 development
2017-05-09 02:51:58 +02:00
Simon Sessingø 8901e7c125 Development
- Added check in `CsrfToken` class to ensure that IV generation is strong and secure.
- Minor optimisations mostly related to PHPDocs and PHPStorm code-inspection.
2017-05-09 02:49:41 +02:00
Simon Sessingø 50e8926272 Added .htaccess example to documentation (issue #232) 2017-05-09 02:31:01 +02:00
Simon Sessingø e44d6bc30b Merge pull request #231 from skipperbent/v3
V3
2017-03-06 02:56:50 +01:00
Simon Sessingø 27ff761d18 Merge pull request #230 from skipperbent/v3-development
V3 development
2017-03-06 02:56:37 +01:00
Simon Sessingø 0a58d36606 Development
- Fixed: `RouteController` not matching certain urls.
- Made `RouteResource` more strict in url-matching.
- Added PHPUnit `RouterControllerTest` class.
- Fixed merged `testSimularUrls` method in `RouterUrlTest`.
2017-03-06 02:53:44 +01:00
Simon Sessingø db024b9588 Added simular routes PHPUnit test 2017-03-06 02:21:13 +01:00
Simon Sessingø 3f61c90749 Merge pull request #229 from skipperbent/v3
V3
2017-03-05 17:10:35 +01:00
Simon Sessingø 94d0764631 Merge pull request #228 from skipperbent/v3-development
Development
2017-03-05 17:10:27 +01:00
Simon Sessingø ff3f1bdcdd Development
- Fixed issue #227 causing custom resource-routes not to be loaded after latest update.
- Optimized `RouteResource` class.
- Renamed `IRestController` to `IResourceController`.
- Added unit-tests for RouterResource.
- Simplified unit-tests with the `TestRouter` custom router class.
2017-03-05 17:09:46 +01:00
Simon Sessingø e4e632dca9 Merge pull request #226 from skipperbent/v3
V3
2017-03-01 03:39:46 +01:00
Simon Sessingø 3e75d6ec0b Merge pull request #225 from skipperbent/v3-development
Development
2017-03-01 03:39:33 +01:00
Simon Sessingø 8c5d8c2dc9 Development
- Fixed: namespace being prepended on `RouterController` routes with absolute namespaces.
- Fixed: match domain on `RouteController` and `RouteResource`.
- Fixed: strict url-matching on `RouteController`. Fix should provide better url-matching.
- Fixed: `RouterResource` always matching first url when having simular urls (ex: `/funny-cat` and `/funny-dog`).
- Added `loadRoutes` method to `Router` class so routes now can be loaded without routing the request (useful when running in Cli etc).
- Removed `getInstance` from `Router` class. Please use `SimpleRouter::router()` for singleton usage instead.
- Added `getRemoteAddr` alias-method for `getIp` in `Request` class.
- Added `getValue` to `IInputItem` interface.
- Other minor optimizations.
- Updated documentation with note on absolute/relative namespaces.
2017-02-26 09:18:35 +01:00
Simon Sessingø a5c4a1f721 Merge pull request #223 from skipperbent/v3
V3
2017-02-15 03:29:24 +01:00
Simon Sessingø 9d2bffcd02 Merge pull request #222 from skipperbent/v3-development
Development
2017-02-15 03:29:16 +01:00
Simon Sessingø 2a448fccd2 Development
- Fixed: only set default namespace on relative callbacks.
- Fixed: default-namespace not being set when calling `SimpleRouter::resource`.
- Minor optimisations.
2017-02-15 03:28:34 +01:00
Simon Sessingø 0bc7fa7bd5 Merge pull request #220 from skipperbent/v3
V3
2017-02-13 06:47:36 +01:00
Simon Sessingø e682cf0b7c Merge pull request #219 from skipperbent/v3-development
V3 development
2017-02-13 06:47:26 +01:00
Simon Sessingø c87298ee24 Optimisations 2017-02-13 06:45:07 +01:00
Simon Sessingø 79414255e0 Development
- Using `$request->setRewriteRoute($route);` rewrite-callback now renders any added middlewares on the route.
- Rewrite callbacks now sets default-namespace on route, if defined.
2017-02-13 06:29:22 +01:00
Simon Sessingø eb036ded3b Merge pull request #218 from skipperbent/v3
V3
2017-02-12 20:48:12 +01:00
Simon Sessingø e913dd98da Merge pull request #217 from skipperbent/v3-development
Fixed: replace empty parameter-values with null.
2017-02-12 20:48:02 +01:00
Simon Sessingø ccd3cf450e Fixed: replace empty parameter-values with null. 2017-02-12 20:43:49 +01:00
Simon Sessingø debe080181 Merge pull request #215 from skipperbent/v3
Merge pull request #214 from skipperbent/v3-development
2017-02-09 13:34:46 +01:00
Simon Sessingø 2654ca67db Merge pull request #214 from skipperbent/v3-development
Optimisations
2017-02-09 13:34:32 +01:00
Simon Sessingø dd99e1d488 Merge pull request #213 from skipperbent/v3-development
Optimisations
2017-02-09 13:34:03 +01:00
Simon Sessingø 86c3e513c3 Optimisations 2017-02-09 13:33:36 +01:00
Simon Sessingø 1c07311c5d Merge pull request #212 from skipperbent/v3
Version 3
2017-02-09 13:24:44 +01:00
Simon Sessingø ce276bd5b7 Version 3
- Easier rewrite management.
- Optimisations.
- Updated documentation.
2017-02-09 13:18:05 +01:00
Simon Sessingø 801c84eb05 Merge pull request #210 from skipperbent/v2
V2
2017-02-01 07:52:38 +01:00
Simon Sessingø ed98519152 Merge pull request #209 from skipperbent/v2-development
Fixed getExtension method in InputFile retuning empty value.
2017-02-01 07:52:29 +01:00
Simon Sessingø 55d031e152 Merge pull request #208 from skipperbent/v2
V2
2017-01-19 18:41:55 +01:00
Simon Sessingø 8509062e00 Merge pull request #207 from skipperbent/v2-development
Development
2017-01-19 18:41:43 +01:00
Simon Sessingø 4b4ab906fb Merge pull request #205 from skipperbent/v2
V2
2016-11-28 14:14:38 +01:00
Simon Sessingø 9617cacc31 Merge pull request #204 from skipperbent/v2-development
getRoute optimisations.
2016-11-28 14:14:29 +01:00
Simon Sessingø 54a824ce44 Merge pull request #203 from skipperbent/v2
V2
2016-11-28 14:01:10 +01:00
Simon Sessingø 99088719ed Merge pull request #202 from skipperbent/v2-development
`getUrl` will now use default-parameter value if specifically set to null when using `url()`.
2016-11-28 14:00:59 +01:00
Simon Sessingø 2062ff4189 Merge pull request #201 from skipperbent/v2
V2
2016-11-28 06:45:16 +01:00
Simon Sessingø 3e41ee28b6 Merge pull request #200 from skipperbent/v2-development
Optimised route-match behavior
2016-11-28 06:45:05 +01:00
Simon Sessingø 13a5f40cd0 Merge pull request #199 from skipperbent/v2
V2
2016-11-28 05:53:36 +01:00
Simon Sessingø 751b4444ae Merge pull request #198 from skipperbent/v2-development
Fixed parameters not merged with default values
2016-11-28 05:53:26 +01:00
Simon Sessingø 351f7a01e8 Merge pull request #197 from skipperbent/v2
V2
2016-11-28 05:26:19 +01:00
Simon Sessingø 8cd42a2c4b Merge pull request #196 from skipperbent/v2-development
2.6.0
2016-11-28 05:26:04 +01:00
Simon Sessingø 1496878ae9 Merge pull request #195 from skipperbent/v2
V2
2016-11-26 05:22:17 +01:00
Simon Sessingø e8e1471bab Merge pull request #194 from skipperbent/v2-development
Input optimisations
2016-11-26 05:22:02 +01:00
Simon Sessingø b3f2e5f812 Merge pull request #193 from skipperbent/v2
V2
2016-11-26 05:03:45 +01:00
Simon Sessingø 4de1498723 Merge pull request #192 from skipperbent/v2-development
V2 development
2016-11-26 05:03:36 +01:00
Simon Sessingø a60fa9f919 Merge pull request #191 from skipperbent/v2
V2
2016-11-25 18:28:37 +01:00
Simon Sessingø ea243f2c89 Merge pull request #190 from skipperbent/v2-development
Development
2016-11-25 18:28:26 +01:00
Simon Sessingø ce6f34f4d4 Merge pull request #189 from skipperbent/v2
V2
2016-11-25 12:55:17 +01:00
Simon Sessingø 56653568e4 Merge pull request #188 from skipperbent/v2-development
Development
2016-11-25 12:55:08 +01:00
Simon Sessingø 2868ffe023 Merge pull request #187 from skipperbent/v2
V2
2016-11-25 03:29:01 +02:00
Simon Sessingø 5d330643e7 Merge pull request #186 from skipperbent/v2-development
Development
2016-11-25 03:28:49 +02:00
Simon Sessingø 8ab52364da Merge pull request #185 from skipperbent/v2
V2
2016-11-25 03:06:03 +02:00
Simon Sessingø 57aa8eac1e Merge pull request #184 from skipperbent/v2-development
Fixed Exception when using Request
2016-11-25 03:05:17 +02:00
Simon Sessingø 110bc2afcf Merge pull request #183 from skipperbent/v2
V2
2016-11-25 02:58:55 +02:00
Simon Sessingø 4efc72d013 Merge pull request #182 from skipperbent/v2-development
Development
2016-11-25 02:58:43 +02:00
Simon Sessingø a92a4cdf0d Merge pull request #181 from skipperbent/v2
V2
2016-11-24 14:51:16 +02:00
Simon Sessingø 49fc991f9a Merge pull request #180 from skipperbent/v2-development
Updated documentation
2016-11-24 14:51:07 +02:00
Simon Sessingø 0a4c78cf64 Merge pull request #179 from skipperbent/v2
V2
2016-11-24 14:45:06 +02:00
Simon Sessingø 20353c6e4d Merge pull request #178 from skipperbent/v2-development
Updated documentation to use Demo instead of MyWebsite
2016-11-24 14:44:56 +02:00
Simon Sessingø 6706d86784 Merge pull request #177 from skipperbent/v2
V2
2016-11-24 13:19:00 +02:00
Simon Sessingø 4c62f86a26 Merge pull request #176 from skipperbent/v2-development
Fixed HttpException not thrown as NotFoundHttpException
2016-11-24 13:18:52 +02:00
Simon Sessingø 20c1e0ef69 Merge pull request #175 from skipperbent/v2
V2
2016-11-24 11:06:10 +02:00
Simon Sessingø 6445746324 Merge pull request #174 from skipperbent/v2-development
Optimisations + bugfixes
2016-11-24 11:05:57 +02:00
Simon Sessingø 1a2921acb4 Merge pull request #173 from skipperbent/v2-development
Development
2016-11-21 09:30:58 +02:00
Simon Sessingø fe560a9ba5 Merge pull request #172 from skipperbent/v2
V2
2016-11-21 09:04:32 +02:00
Simon Sessingø 258d9d05c7 Merge pull request #171 from skipperbent/v2-development
Enhancements
2016-11-21 09:04:22 +02:00
Simon Sessingø c522801c28 Merge pull request #170 from skipperbent/v2
V2
2016-11-21 05:28:00 +02:00
Simon Sessingø 7e7319de06 Merge pull request #169 from skipperbent/v2-development
Fixed // on currentRoute urls.
2016-11-21 05:27:53 +02:00
Simon Sessingø 9d275d6d3b Merge pull request #168 from skipperbent/v2-development
Bugfixes
2016-11-21 05:08:12 +02:00
Simon Sessingø 531b35532b Merge pull request #167 from skipperbent/v2-development
Bugfixes
2016-11-21 05:07:59 +02:00
Simon Sessingø e6dd9f3f55 Merge pull request #166 from skipperbent/v2
V2
2016-11-21 04:09:35 +02:00
Simon Sessingø c258f937e8 Merge pull request #165 from skipperbent/v2-development
V 2.4.0 development
2016-11-21 04:09:26 +02:00
Simon Sessingø e803b1ca5c Merge pull request #164 from skipperbent/v2
V2
2016-11-19 21:13:12 +02:00
Simon Sessingø 88d58cd7b7 Merge pull request #163 from skipperbent/v2-development
Fixed: wrong argument-type in getUrl method
2016-11-19 21:12:57 +02:00
Simon Sessingø 45faf9830e Merge pull request #162 from skipperbent/v2
V2
2016-11-19 20:41:38 +02:00
Simon Sessingø d04c74ccad Merge pull request #161 from skipperbent/v2-development
V2 development
2016-11-19 20:41:26 +02:00
Simon Sessingø c915c0386a Merge pull request #160 from skipperbent/v2
V2
2016-11-19 10:16:06 +02:00
Simon Sessingø 02809a4daf Merge pull request #159 from skipperbent/v2-development
Bugfixes
2016-11-19 10:15:56 +02:00
Simon Sessingø 30479b15ca Merge pull request #158 from skipperbent/v2
V2
2016-11-19 09:47:29 +02:00
Simon Sessingø da6b5af19f Merge pull request #157 from skipperbent/v2-development
Bugfixes
2016-11-19 09:47:19 +02:00
Simon Sessingø 4dde51e833 Merge pull request #156 from skipperbent/v2
V2
2016-11-19 07:02:34 +02:00
Simon Sessingø 914ec9d1b7 Merge pull request #155 from skipperbent/v2-development
Small optimisations
2016-11-19 07:02:23 +02:00
Simon Sessingø 961a4d0e94 Merge pull request #154 from skipperbent/v2
V2
2016-11-19 06:25:23 +02:00
Simon Sessingø 5f72755a98 Merge pull request #153 from skipperbent/v2-development
V2 development
2016-11-19 06:25:08 +02:00
Simon Sessingø 2216090a5f Merge pull request #152 from skipperbent/v2
V2
2016-11-17 19:01:25 +02:00
Simon Sessingø 41d15d3acd Merge pull request #151 from skipperbent/v2-development
Re-added missing methods from version 1.
2016-11-17 19:01:17 +02:00
Simon Sessingø d467f60e55 Merge pull request #150 from skipperbent/v2
V2
2016-11-17 17:33:59 +02:00
Simon Sessingø ded9c8ebe0 Merge pull request #149 from skipperbent/v2-development
Updated documentation
2016-11-17 17:33:49 +02:00
Simon Sessingø c30ae6b098 Merge pull request #148 from skipperbent/v2
V2
2016-11-17 17:25:54 +02:00
Simon Sessingø 394f7beb8b Merge pull request #147 from skipperbent/v2-development
V2 development
2016-11-17 17:25:30 +02:00
Simon Sessingø c93308c8e1 Merge pull request #146 from skipperbent/v2
V2
2016-11-17 16:04:53 +02:00
Simon Sessingø baab004482 Merge pull request #145 from skipperbent/v2-development
Added default null parameter value to RouterController and RouterResource
2016-11-17 16:04:38 +02:00
Simon Sessingø c3241012af Merge pull request #144 from skipperbent/v2
V2
2016-11-17 15:21:32 +02:00
Simon Sessingø 00c0cad211 Merge pull request #143 from skipperbent/v2-development
Bugfix: set parameters to null when new url is set
2016-11-17 15:21:24 +02:00
Simon Sessingø 9e2ba2674b Merge pull request #142 from skipperbent/v2
V2
2016-11-17 06:37:11 +02:00
Simon Sessingø c6e85676da Merge pull request #141 from skipperbent/v2-development
Bugfixes
2016-11-17 06:37:03 +02:00
Simon Sessingø 4d18f33a81 Merge pull request #140 from skipperbent/v2
V2
2016-11-17 05:03:39 +02:00
Simon Sessingø 5a74c9d27d Merge pull request #139 from skipperbent/v2-development
Development
2016-11-17 05:03:30 +02:00
Simon Sessingø 9d98b2da4c Merge pull request #138 from skipperbent/v2
V2
2016-11-16 16:16:33 +02:00
Simon Sessingø b5eef6f3ee Merge pull request #137 from skipperbent/v2-development
Bugfixes
2016-11-16 16:16:22 +02:00
Simon Sessingø 6930864e7e Merge pull request #136 from skipperbent/v2
V2
2016-11-16 15:32:45 +02:00
Simon Sessingø ad0eceb814 Merge pull request #135 from skipperbent/v2-development
Bugfixes
2016-11-16 15:32:32 +02:00
Simon Sessingø f3f129ae0b Merge pull request #134 from skipperbent/v2
V2
2016-11-15 09:47:07 +02:00
Simon Sessingø 4bec2bc5fb Merge pull request #133 from skipperbent/v2-development
Csrf bugfixes
2016-11-15 09:46:54 +02:00
Simon Sessingø 85b9fac21e Merge pull request #132 from skipperbent/v2
V2
2016-11-15 08:13:22 +02:00
Simon Sessingø 4f47463497 Merge pull request #131 from skipperbent/v2-development
V2 development
2016-11-15 08:13:09 +02:00
Simon Sessingø 0c93633d13 Merge pull request #130 from skipperbent/v2
V2
2016-11-15 01:12:51 +02:00
Simon Sessingø e5c86c1822 Merge pull request #129 from skipperbent/v2-development
Bugfixes
2016-11-15 01:12:41 +02:00
Simon Sessingø 973344e46e Merge pull request #128 from skipperbent/v2
V2
2016-11-08 18:23:22 +02:00
Simon Sessingø 32c305bd2c Merge pull request #124 from skipperbent/v2-development
V2 development
2016-11-08 18:22:49 +02:00
Simon Sessingø 4a03005c68 Merge pull request #122 from skipperbent/development
Fixed urls in groups not working
2016-11-06 09:04:56 +01:00
Simon Sessingø 52034411cf Merge pull request #121 from skipperbent/development
Development
2016-11-06 08:14:36 +01:00
Simon Sessingø 4c8ed5bb3d Merge pull request #120 from skipperbent/development
Development
2016-11-05 23:09:19 +01:00
Simon Sessingø 9fed6ffb3f Merge pull request #119 from skipperbent/development
Optimised for cli-usage
2016-10-28 07:41:21 +02:00
Simon Sessingø 15da599e82 Merge pull request #118 from skipperbent/development
Minor optimisations
2016-10-27 19:15:59 +02:00
Simon Sessingø 9274acb591 Merge pull request #117 from skipperbent/development
Optimisations and bugfixes
2016-10-27 17:06:24 +02:00
Simon Sessingø 5c7759ab72 Merge pull request #116 from skipperbent/development
Development
2016-10-27 16:45:29 +02:00
Simon Sessingø c7b8593185 Merge pull request #114 from skipperbent/development
Development
2016-10-20 08:38:23 +02:00
Simon Sessingø fb478f475c Merge pull request #113 from skipperbent/development
Development
2016-10-20 08:35:20 +02:00
Simon Sessingø 1142d9d4ce Merge pull request #112 from skipperbent/development
Optimised middleware load order
2016-09-28 12:30:06 +02:00
Simon Sessingø 5d5c96e802 Merge pull request #111 from skipperbent/development
Automatically push middlewares if multiple in nested group.
2016-09-28 12:24:38 +02:00
Simon Sessingø bfdaf8ac52 Merge pull request #110 from skipperbent/development
Updated documentation and added demo-project.
2016-09-26 14:39:50 +02:00
Simon Sessingø 71dc6e172f Merge pull request #108 from skipperbent/development
Allow for default parameters (including A-Z, a-z, 0-9 and "-") when p…
2016-06-14 22:21:20 +02:00
Simon Sessingø 6ee172927f Merge pull request #107 from skipperbent/development
Bugfix
2016-06-04 18:44:59 +02:00
Simon Sessingø bb5e629199 Merge pull request #106 from skipperbent/development
Development
2016-06-04 18:21:16 +02:00
Simon Sessingø 0cc0a59fd5 Merge pull request #105 from skipperbent/development
Development
2016-06-04 15:13:47 +02:00
Simon Sessingø 498fd6b07d Merge pull request #103 from skipperbent/development
Added setValue method to InputItem class.
2016-05-04 13:41:36 +02:00
Simon Sessingø 96ab22a4f8 Merge pull request #102 from skipperbent/development
Added exist method to Input class.
2016-05-03 07:21:16 +02:00
Simon Sessingø 7f528c133b Merge pull request #101 from skipperbent/development
Development
2016-05-01 02:01:47 +02:00
Simon Sessingø 5a50190293 Merge pull request #100 from skipperbent/development
Development
2016-04-25 00:41:50 +02:00
Simon Sessingø 355ef01d63 Merge pull request #99 from skipperbent/development
Development
2016-04-22 15:38:02 +02:00
Simon Sessingø d3162b5a2b Merge pull request #98 from skipperbent/development
Development
2016-04-22 14:30:40 +02:00
Simon Sessingø 810b80487d Merge pull request #97 from skipperbent/development
- Added custom ExceptionHandler example to documentation.
2016-04-21 08:30:34 +02:00
Simon Sessingø 18a9df56ca Merge pull request #96 from skipperbent/development
Development
2016-04-20 08:10:18 +02:00
Simon Sessingø 6e14ded03f Merge pull request #95 from skipperbent/development
Development
2016-04-16 23:23:56 +02:00
Simon Sessingø 899081f8d8 Merge pull request #94 from skipperbent/development
Update README.md
2016-04-16 00:01:22 +02:00
Simon Sessingø e7b9206bc9 Merge pull request #93 from skipperbent/development
Development
2016-04-15 23:21:00 +02:00
Simon Sessingø cd6e800984 Merge pull request #91 from skipperbent/development
Development
2016-04-15 23:14:55 +02:00
Simon Sessingø 11bd5a7d11 Merge pull request #89 from skipperbent/development
[BUGFIX] Fixed notice
2016-04-09 15:39:12 +02:00
Simon Sessingø be32796b01 Merge pull request #88 from skipperbent/development
[TASK] Moved group-middleware rendering to routeRequest to ensure all…
2016-04-09 15:32:45 +02:00
Simon Sessingø 8b3d71a328 Merge pull request #87 from skipperbent/development
[BUGFIX] Fixed only match group route if prefix is set
2016-04-09 10:02:22 +02:00
Simon Sessingø 1fae638aaf Merge pull request #86 from skipperbent/development
Development
2016-04-09 09:50:56 +02:00
Simon Sessingø 37c8bc9f32 Merge pull request #79 from skipperbent/development
[BUGFIX] Bugfixes and optimisations
2016-04-07 23:21:13 +02:00
Simon Sessingø 75029b330a Merge pull request #78 from skipperbent/development
[BUGFIX] Fixed nested groups not merging settings to routes
2016-04-07 19:34:02 +02:00
Simon Sessingø fd5d893040 Merge pull request #77 from skipperbent/development
[TASK] Added rewrite_uri parameter to Request class
2016-03-19 19:12:42 +01:00
Simon Sessingø c1512740af Merge pull request #76 from skipperbent/development
[BUGFIX] Readded rendering of groups
2016-03-19 18:59:01 +01:00
Simon Sessingø 212ae133de Merge pull request #75 from skipperbent/development
[TASK] Readded merging
2016-03-19 17:40:26 +01:00
Simon Sessingø ae58231fa1 Merge pull request #74 from skipperbent/development
Custom boot-managers
2016-03-19 16:29:03 +01:00
Simon Sessingø 358b25d4f1 Merge pull request #73 from skipperbent/development
Development
2016-03-18 17:55:31 +01:00
Simon Sessingø 3d45851d9b Merge pull request #72 from skipperbent/development
[BUGFIX] Only render group if prefix matches.
2016-03-16 19:58:03 +01:00
Simon Sessingø ee5c2207f8 Merge pull request #71 from skipperbent/development
Added support for x-forwarded-proto http header
2016-03-14 01:08:25 +01:00
Simon Sessingø b1ca3fc9ef Merge pull request #70 from skipperbent/development
[FEATURE] Added http code to redirect method.
2016-03-14 00:59:09 +01:00
Simon Sessingø 253c0c70d4 Merge pull request #69 from skipperbent/development
[BUGFIX] Bugfix
2016-03-01 22:52:38 +01:00
Simon Sessingø 53ba2d7ac5 Merge pull request #68 from skipperbent/development
[TASK] Fixed regex causing optional parameters to sometimes catch req…
2016-01-23 16:12:27 +01:00
Simon Sessingø 315fe05769 Merge pull request #67 from skipperbent/development
Development
2016-01-17 04:57:39 +01:00
Simon Sessingø a57113309a Merge pull request #66 from skipperbent/development
Development
2016-01-15 11:56:04 +01:00
95 changed files with 7196 additions and 2239 deletions
+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/
demo-project/vendor
.idea/
.phpunit.result.cache
+1170 -335
View File
File diff suppressed because it is too large Load Diff
+45 -24
View File
@@ -1,26 +1,47 @@
{
"name": "pecee/simple-router",
"description": "Simple, fast PHP router that is easy to get integrated and in almost any project. Heavily inspired by the Laravel router.",
"keywords": [ "router", "routing", "laravel", "pecee" ],
"license": "MIT",
"support": {
"source": "https://github.com/skipperbent/simple-php-router/issues"
},
"authors": [
{
"name": "Simon Sessingø",
"email": "simon.sessingoe@gmail.com"
}
],
"require": {
"php": ">=5.4.0"
},
"require-dev": {
"phpunit/phpunit": "4.7.7"
},
"autoload": {
"psr-4": {
"Pecee\\": "src/Pecee/"
}
"name": "pecee/simple-router",
"description": "Simple, fast PHP router that is easy to get integrated and in almost any project. Heavily inspired by the Laravel router.",
"keywords": [
"router",
"router",
"routing",
"route",
"simple-php-router",
"laravel",
"pecee",
"php",
"framework",
"url-handling",
"input-handler",
"routing-engine",
"request-handler"
],
"license": "MIT",
"support": {
"source": "https://github.com/skipperbent/simple-php-router/issues"
},
"authors": [
{
"name": "Simon Sessingø",
"email": "simon.sessingoe@gmail.com"
}
}
],
"require": {
"php": ">=7.1",
"ext-json": "*"
},
"require-dev": {
"phpunit/phpunit": "^7",
"mockery/mockery": "^1"
},
"scripts": {
"test": [
"phpunit tests"
]
},
"autoload": {
"psr-4": {
"Pecee\\": "src/Pecee/"
}
}
}
+88
View File
@@ -0,0 +1,88 @@
<?php
use Pecee\SimpleRouter\SimpleRouter as Router;
use Pecee\Http\Url;
use Pecee\Http\Response;
use Pecee\Http\Request;
/**
* Get url for a route by using either name/alias, class or method name.
*
* The name parameter supports the following values:
* - Route name
* - Controller/resource name (with or without method)
* - Controller class name
*
* When searching for controller/resource by name, you can use this syntax "route.name@method".
* You can also use the same syntax when searching for a specific controller-class "MyController@home".
* If no arguments is specified, it will return the url for the current loaded route.
*
* @param string|null $name
* @param string|array|null $parameters
* @param array|null $getParams
* @return \Pecee\Http\Url
* @throws \InvalidArgumentException
*/
function url(?string $name = null, $parameters = null, ?array $getParams = null): Url
{
return Router::getUrl($name, $parameters, $getParams);
}
/**
* @return \Pecee\Http\Response
*/
function response(): Response
{
return Router::response();
}
/**
* @return \Pecee\Http\Request
*/
function request(): Request
{
return Router::request();
}
/**
* Get input class
* @param string|null $index Parameter index name
* @param string|mixed|null $defaultValue Default return value
* @param array ...$methods Default methods
* @return \Pecee\Http\Input\InputHandler|array|string|null
*/
function input($index = null, $defaultValue = null, ...$methods)
{
if ($index !== null) {
return request()->getInputHandler()->value($index, $defaultValue, ...$methods);
}
return request()->getInputHandler();
}
/**
* @param string $url
* @param int|null $code
*/
function redirect(string $url, ?int $code = null): void
{
if ($code !== null) {
response()->httpCode($code);
}
response()->redirect($url);
}
/**
* Get current csrf-token
* @return string|null
*/
function csrf_token(): ?string
{
$baseVerifier = Router::router()->getCsrfVerifier();
if ($baseVerifier !== null) {
return $baseVerifier->getTokenProvider()->getToken();
}
return null;
}
+24
View File
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
backupGlobals="false"
backupStaticAttributes="false"
colors="true"
bootstrap="tests/bootstrap.php"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false">
<testsuites>
<testsuite name="SimpleRouter Test Suite">
<directory>tests/Pecee/SimpleRouter/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist addUncoveredFilesFromWhitelist="true"
processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">src</directory>
</whitelist>
</filter>
</phpunit>
@@ -0,0 +1,48 @@
<?php
namespace Pecee\Controllers;
interface IResourceController
{
/**
* @return string|null
*/
public function index(): ?string;
/**
* @param mixed $id
* @return string|null
*/
public function show($id): ?string;
/**
* @return string|null
*/
public function store(): ?string;
/**
* @return string|null
*/
public function create(): ?string;
/**
* View
* @param mixed $id
* @return string|null
*/
public function edit($id): ?string;
/**
* @param mixed $id
* @return string|null
*/
public function update($id): ?string;
/**
* @param mixed $id
* @return string|null
*/
public function destroy($id): ?string;
}
-47
View File
@@ -1,47 +0,0 @@
<?php
namespace Pecee\Controllers;
interface IRestController
{
/**
* @return void
*/
public function index();
/**
* @param mixed $id
* @return void
*/
public function show($id);
/**
* @return void
*/
public function store();
/**
* @return void
*/
public function create();
/**
* View
* @param mixed $id
* @return void
*/
public function edit($id);
/**
* @param mixed $id
* @return void
*/
public function update($id);
/**
* @param mixed $id
* @return void
*/
public function destroy($id);
}
-71
View File
@@ -1,71 +0,0 @@
<?php
namespace Pecee;
class CsrfToken
{
const CSRF_KEY = 'XSRF-TOKEN';
protected $token;
/**
* Generate random identifier for CSRF token
*
* @return string
*/
public static function generateToken()
{
if (function_exists('random_bytes')) {
return bin2hex(random_bytes(32));
}
return bin2hex(openssl_random_pseudo_bytes(32));
}
/**
* Validate valid CSRF token
*
* @param string $token
* @return bool
*/
public function validate($token)
{
if ($token !== null && $this->getToken() !== null) {
return hash_equals($token, $this->getToken());
}
return false;
}
/**
* Set csrf token cookie
*
* @param $token
*/
public function setToken($token)
{
setcookie(static::CSRF_KEY, $token, time() + 60 * 120, '/');
}
/**
* Get csrf token
* @return string|null
*/
public function getToken()
{
if ($this->hasToken()) {
return $_COOKIE[static::CSRF_KEY];
}
return null;
}
/**
* Returns whether the csrf token has been defined
* @return bool
*/
public function hasToken()
{
return isset($_COOKIE[static::CSRF_KEY]);
}
}
@@ -0,0 +1,8 @@
<?php
namespace Pecee\Exceptions;
class InvalidArgumentException extends \InvalidArgumentException
{
}
-17
View File
@@ -1,17 +0,0 @@
<?php
namespace Pecee\Handlers;
use Pecee\Http\Request;
use Pecee\SimpleRouter\Route\ILoadableRoute;
interface IExceptionHandler
{
/**
* @param Request $request
* @param ILoadableRoute $route
* @param \Exception $error
* @return Request|null
*/
public function handleError(Request $request, ILoadableRoute &$route = null, \Exception $error);
}
@@ -0,0 +1,8 @@
<?php
namespace Pecee\Http\Exceptions;
class MalformedUrlException extends \Exception
{
}
+10 -5
View File
@@ -1,17 +1,22 @@
<?php
namespace Pecee\Http\Input;
interface IInputItem
{
public function getIndex();
public function getIndex(): string;
public function setIndex($index);
public function setIndex(string $index): self;
public function getName();
public function getName(): ?string;
public function setName($name);
public function setName(string $name): self;
public function __toString();
public function getValue();
public function setValue($value): self;
public function __toString(): string;
}
-295
View File
@@ -1,295 +0,0 @@
<?php
namespace Pecee\Http\Input;
use Pecee\Http\Request;
class Input
{
/**
* @var array
*/
public $get = [];
/**
* @var array
*/
public $post = [];
/**
* @var array
*/
public $file = [];
/**
* @var Request
*/
protected $request;
public function __construct(Request $request)
{
$this->request = $request;
$this->parseInputs();
}
public function parseInputs()
{
/* Parse get requests */
if (count($_GET) > 0) {
$this->get = $this->handleGetPost($_GET);
}
/* Parse post requests */
$postVars = $_POST;
if (in_array($this->request->getMethod(), ['put', 'patch', 'delete'], false) === true) {
parse_str(file_get_contents('php://input'), $postVars);
}
if (count($postVars) > 0) {
$this->post = $this->handleGetPost($postVars);
}
/* Parse get requests */
if (count($_FILES) > 0) {
$this->file = $this->parseFiles();
}
}
public function parseFiles()
{
$list = [];
foreach ($_FILES as $key => $value) {
// Handle array input
if (is_array($value['name']) === false) {
$values['index'] = $key;
$list[$key] = InputFile::createFromArray(array_merge($value, $values));
continue;
}
$keys = [];
$files = $this->rearrangeFiles($value['name'], $keys, $value);
if (isset($list[$key])) {
$list[$key][] = $files;
} else {
$list[$key] = $files;
}
}
return $list;
}
protected function rearrangeFiles(array $values, &$index, $original)
{
$output = [];
$getItem = function ($key, $property = 'name') use ($original, $index) {
$path = $original[$property];
foreach (array_values($index) as $i) {
$path = $path[$i];
}
return $path[$key];
};
foreach ($values as $key => $value) {
if (is_array($getItem($key)) === false) {
$file = InputFile::createFromArray([
'index' => $key,
'error' => $getItem($key, 'error'),
'tmp_name' => $getItem($key, 'tmp_name'),
'type' => $getItem($key, 'type'),
'size' => $getItem($key, 'size'),
'filename' => $getItem($key, 'name'),
]);
if (isset($output[$key])) {
$output[$key][] = $file;
} else {
$output[$key] = $file;
}
continue;
}
$index[] = $key;
$files = $this->rearrangeFiles($value, $index, $original);
if (isset($output[$key])) {
$output[$key][] = $files;
} else {
$output[$key] = $files;
}
}
return $output;
}
protected function handleGetPost(array $array)
{
$list = [];
$max = count($array) - 1;
$keys = array_keys($array);
for ($i = $max; $i >= 0; $i--) {
$key = $keys[$i];
$value = $array[$key];
// Handle array input
if (is_array($value) === false) {
$list[$key] = new InputItem($key, $value);
continue;
}
$output = $this->handleGetPost($value);
$list[$key] = $output;
}
return $list;
}
/**
* Find post-value by index or return default value.
*
* @param string $index
* @param string|null $defaultValue
* @return InputItem|string
*/
public function findPost($index, $defaultValue = null)
{
return isset($this->post[$index]) ? $this->post[$index] : $defaultValue;
}
/**
* Find file by index or return default value.
*
* @param string $index
* @param string|null $defaultValue
* @return InputFile|string
*/
public function findFile($index, $defaultValue = null)
{
return isset($this->file[$index]) ? $this->file[$index] : $defaultValue;
}
/**
* Find parameter/query-string by index or return default value.
*
* @param string $index
* @param string|null $defaultValue
* @return InputItem|string
*/
public function findGet($index, $defaultValue = null)
{
return isset($this->get[$index]) ? $this->get[$index] : $defaultValue;
}
/**
* Get input object
*
* @param string $index
* @param string|null $defaultValue
* @param array|string|null $methods
* @return IInputItem|string
*/
public function getObject($index, $defaultValue = null, $methods = null)
{
if ($methods !== null && is_string($methods) === true) {
$methods = [$methods];
}
$element = null;
if ($methods === null || in_array('get', $methods)) {
$element = $this->findGet($index);
}
if (($element === null && $methods === null) || ($methods !== null && in_array('post', $methods))) {
$element = $this->findPost($index);
}
if (($element === null && $methods === null) || ($methods !== null && in_array('file', $methods))) {
$element = $this->findFile($index);
}
return ($element !== null) ? $element : $defaultValue;
}
/**
* Get input element value matching index
*
* @param string $index
* @param string|null $defaultValue
* @param array|string|null $methods
* @return InputItem|string
*/
public function get($index, $defaultValue = null, $methods = null)
{
$input = $this->getObject($index, $defaultValue, $methods);
if ($input instanceof InputItem) {
return (trim($input->getValue()) === '') ? $defaultValue : $input->getValue();
}
return $input;
}
/**
* Check if a input-item exist
*
* @param string $index
* @return bool
*/
public function exists($index)
{
return ($this->getObject($index) !== null);
}
/**
* Get all get/post items
* @param array|null $filter Only take items in filter
* @return array
*/
public function all(array $filter = null)
{
$output = $_POST;
if ($this->request->getMethod() === 'post') {
$contents = file_get_contents('php://input');
if (strpos(trim($contents), '{') === 0) {
$output = json_decode($contents, true);
if ($output === false) {
$output = [];
}
}
}
$output = array_merge($_GET, $output);
if ($filter !== null) {
$output = array_filter($output, function ($key) use ($filter) {
return (in_array($key, $filter) === true);
}, ARRAY_FILTER_USE_KEY);
}
return $output;
}
}
+67 -45
View File
@@ -1,6 +1,9 @@
<?php
namespace Pecee\Http\Input;
use Pecee\Exceptions\InvalidArgumentException;
class InputFile implements IInputItem
{
public $index;
@@ -8,43 +11,45 @@ class InputFile implements IInputItem
public $filename;
public $size;
public $type;
public $error;
public $errors;
public $tmpName;
public function __construct($index)
public function __construct(string $index)
{
$this->index = $index;
$this->errors = 0;
// Make the name human friendly, by replace _ with space
$this->name = ucfirst(str_replace('_', ' ', $this->index));
$this->name = ucfirst(str_replace('_', ' ', strtolower($this->index)));
}
/**
* Create from array
*
* @param array $values
* @throws \InvalidArgumentException
* @throws InvalidArgumentException
* @return static
*/
public static function createFromArray(array $values)
public static function createFromArray(array $values): self
{
if (!isset($values['index'])) {
throw new \InvalidArgumentException('Index key is required');
if (isset($values['index']) === false) {
throw new InvalidArgumentException('Index key is required');
}
/* Easy way of ensuring that all indexes-are set and not filling the screen with isset() */
$values = array_merge([
$values += [
'tmp_name' => null,
'type' => null,
'size' => null,
'name' => null,
'error' => null,
], $values);
];
return (new static($values['index']))
->setError($values['error'])
->setSize($values['size'])
->setSize((int)$values['size'])
->setError((int)$values['error'])
->setType($values['type'])
->setTmpName($values['tmp_name'])
->setFilename($values['name']);
@@ -54,7 +59,7 @@ class InputFile implements IInputItem
/**
* @return string
*/
public function getIndex()
public function getIndex(): string
{
return $this->index;
}
@@ -62,9 +67,9 @@ class InputFile implements IInputItem
/**
* Set input index
* @param string $index
* @return static $this
* @return static
*/
public function setIndex($index)
public function setIndex(string $index): IInputItem
{
$this->index = $index;
@@ -74,7 +79,7 @@ class InputFile implements IInputItem
/**
* @return string
*/
public function getSize()
public function getSize(): string
{
return $this->size;
}
@@ -82,9 +87,9 @@ class InputFile implements IInputItem
/**
* Set file size
* @param int $size
* @return static $this
* @return static
*/
public function setSize($size)
public function setSize(int $size): IInputItem
{
$this->size = $size;
@@ -95,7 +100,7 @@ class InputFile implements IInputItem
* Get mime-type of file
* @return string
*/
public function getMime()
public function getMime(): string
{
return $this->getType();
}
@@ -103,7 +108,7 @@ class InputFile implements IInputItem
/**
* @return string
*/
public function getType()
public function getType(): string
{
return $this->type;
}
@@ -111,9 +116,9 @@ class InputFile implements IInputItem
/**
* Set type
* @param string $type
* @return static $this
* @return static
*/
public function setType($type)
public function setType(string $type): IInputItem
{
$this->type = $type;
@@ -125,7 +130,7 @@ class InputFile implements IInputItem
*
* @return string
*/
public function getExtension()
public function getExtension(): string
{
return pathinfo($this->getFilename(), PATHINFO_EXTENSION);
}
@@ -135,7 +140,7 @@ class InputFile implements IInputItem
*
* @return string
*/
public function getName()
public function getName(): ?string
{
return $this->name;
}
@@ -145,9 +150,9 @@ class InputFile implements IInputItem
* Useful for adding validation etc.
*
* @param string $name
* @return static $this
* @return static
*/
public function setName($name)
public function setName(string $name): IInputItem
{
$this->name = $name;
@@ -158,9 +163,9 @@ class InputFile implements IInputItem
* Set filename
*
* @param string $name
* @return static $this
* @return static
*/
public function setFilename($name)
public function setFilename($name): IInputItem
{
$this->filename = $name;
@@ -172,7 +177,7 @@ class InputFile implements IInputItem
*
* @return string mixed
*/
public function getFilename()
public function getFilename(): ?string
{
return $this->filename;
}
@@ -183,7 +188,7 @@ class InputFile implements IInputItem
* @param string $destination
* @return bool
*/
public function move($destination)
public function move($destination): bool
{
return move_uploaded_file($this->tmpName, $destination);
}
@@ -193,17 +198,17 @@ class InputFile implements IInputItem
*
* @return string
*/
public function getContents()
public function getContents(): string
{
return file_get_contents($this->tmpName);
}
/**
* Return true if an upload error occured.
* Return true if an upload error occurred.
*
* @return bool
*/
public function hasError()
public function hasError(): bool
{
return ($this->getError() !== 0);
}
@@ -211,22 +216,22 @@ class InputFile implements IInputItem
/**
* Get upload-error code.
*
* @return string
* @return int
*/
public function getError()
public function getError(): int
{
return $this->error;
return (int)$this->errors;
}
/**
* Set error
*
* @param int $error
* @return static $this
* @return static
*/
public function setError($error)
public function setError($error): IInputItem
{
$this->error = (int)$error;
$this->errors = (int)$error;
return $this;
}
@@ -234,7 +239,7 @@ class InputFile implements IInputItem
/**
* @return string
*/
public function getTmpName()
public function getTmpName(): string
{
return $this->tmpName;
}
@@ -242,28 +247,45 @@ class InputFile implements IInputItem
/**
* Set file temp. name
* @param string $name
* @return static $this
* @return static
*/
public function setTmpName($name)
public function setTmpName($name): IInputItem
{
$this->tmpName = $name;
return $this;
}
public function __toString()
public function __toString(): string
{
return $this->getTmpName();
}
public function toArray()
public function getValue()
{
return $this->getFilename();
}
/**
* @param mixed $value
* @return static
*/
public function setValue($value): IInputItem
{
$this->filename = $value;
return $this;
}
public function toArray(): array
{
return [
'tmp_name' => $this->tmpName,
'type' => $this->type,
'size' => $this->size,
'name' => $this->filename,
'error' => $this->error,
'name' => $this->name,
'error' => $this->errors,
'filename' => $this->filename,
];
}
+454
View File
@@ -0,0 +1,454 @@
<?php
namespace Pecee\Http\Input;
use Pecee\Exceptions\InvalidArgumentException;
use Pecee\Http\Request;
class InputHandler
{
/**
* @var array
*/
protected $get = [];
/**
* @var array
*/
protected $post = [];
/**
* @var array
*/
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
*/
public function __construct(Request $request)
{
$this->request = $request;
$this->parseInputs();
}
/**
* Parse input values
*
*/
public function parseInputs(): void
{
/* Parse get requests */
if (\count($_GET) !== 0) {
$this->originalParams = $_GET;
$this->get = $this->parseInputItem($this->originalParams);
}
/* Parse post requests */
$this->originalPost = $_POST;
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($this->originalPost) !== 0) {
$this->post = $this->parseInputItem($this->originalPost);
}
/* Parse get requests */
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 $files, ?string $parentKey = null): array
{
$list = [];
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'] = $parentKey ?? $key;
try {
$list[$key] = InputFile::createFromArray($values + $value);
} catch (InvalidArgumentException $e) {
}
continue;
}
$keys = [$key];
$files = $this->rearrangeFile($value['name'], $keys, $value);
if (isset($list[$key]) === true) {
$list[$key][] = $files;
} else {
$list[$key] = $files;
}
}
return $list;
}
/**
* 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);
$output = [];
foreach ($values as $key => $value) {
if (\is_array($original['name'][$key]) === false) {
try {
$file = InputFile::createFromArray([
'index' => (empty($key) === true && empty($originalIndex) === false) ? $originalIndex : $key,
'name' => $original['name'][$key],
'error' => $original['error'][$key],
'tmp_name' => $original['tmp_name'][$key],
'type' => $original['type'][$key],
'size' => $original['size'][$key],
]);
if (isset($output[$key]) === true) {
$output[$key][] = $file;
continue;
}
$output[$key] = $file;
continue;
} catch (InvalidArgumentException $e) {
}
}
$index[] = $key;
$files = $this->rearrangeFile($value, $index, $original);
if (isset($output[$key]) === true) {
$output[$key][] = $files;
} else {
$output[$key] = $files;
}
}
return $output;
}
/**
* 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) === true) {
$value = $this->parseInputItem($value);
}
$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 || \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|array|string|null
*/
public function post(string $index, ?string $defaultValue = null)
{
return $this->post[$index] ?? $defaultValue;
}
/**
* Find file by index or return default value.
*
* @param string $index
* @param string|null $defaultValue
* @return InputFile|array|string|null
*/
public function file(string $index, ?string $defaultValue = null)
{
return $this->file[$index] ?? $defaultValue;
}
/**
* Find parameter/query-string by index or return default value.
*
* @param string $index
* @param string|null $defaultValue
* @return InputItem|array|string|null
*/
public function get(string $index, ?string $defaultValue = null)
{
return $this->get[$index] ?? $defaultValue;
}
/**
* Get all get/post items
* @param array $filter Only take items in filter
* @return array
*/
public function all(array $filter = []): array
{
$output = $this->originalParams + $this->originalPost + $this->originalFile;
$output = (\count($filter) > 0) ? \array_intersect_key($output, \array_flip($filter)) : $output;
foreach ($filter as $filterKey) {
if (array_key_exists($filterKey, $output) === false) {
$output[$filterKey] = null;
}
}
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;
}
}
+20 -14
View File
@@ -1,30 +1,31 @@
<?php
namespace Pecee\Http\Input;
class InputItem implements IInputItem
class InputItem implements IInputItem, \IteratorAggregate
{
public $index;
public $name;
public $value;
public function __construct($index, $value = null)
public function __construct(string $index, $value = null)
{
$this->index = $index;
$this->value = $value;
// Make the name human friendly, by replace _ with space
$this->name = ucfirst(str_replace('_', ' ', $this->index));
$this->name = ucfirst(str_replace('_', ' ', strtolower($this->index)));
}
/**
* @return string
*/
public function getIndex()
public function getIndex(): string
{
return $this->index;
}
public function setIndex($index)
public function setIndex(string $index): IInputItem
{
$this->index = $index;
@@ -34,7 +35,7 @@ class InputItem implements IInputItem
/**
* @return string
*/
public function getName()
public function getName(): ?string
{
return $this->name;
}
@@ -42,9 +43,9 @@ class InputItem implements IInputItem
/**
* Set input name
* @param string $name
* @return static $this
* @return static
*/
public function setName($name)
public function setName(string $name): IInputItem
{
$this->name = $name;
@@ -52,7 +53,7 @@ class InputItem implements IInputItem
}
/**
* @return string
* @return mixed
*/
public function getValue()
{
@@ -61,19 +62,24 @@ class InputItem implements IInputItem
/**
* Set input value
* @param string $value
* @return static $this
* @param mixed $value
* @return static
*/
public function setValue($value)
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());
}
}
+41 -43
View File
@@ -1,26 +1,26 @@
<?php
namespace Pecee\Http\Middleware;
use Pecee\CsrfToken;
use Pecee\Http\Middleware\Exceptions\TokenMismatchException;
use Pecee\Http\Request;
use Pecee\SimpleRouter\Route\ILoadableRoute;
use Pecee\Http\Security\CookieTokenProvider;
use Pecee\Http\Security\ITokenProvider;
class BaseCsrfVerifier implements IMiddleware
{
const POST_KEY = 'csrf-token';
const HEADER_KEY = 'X-CSRF-TOKEN';
public const POST_KEY = 'csrf_token';
public const HEADER_KEY = 'X-CSRF-TOKEN';
protected $except;
protected $csrfToken;
protected $token;
protected $tokenProvider;
/**
* BaseCsrfVerifier constructor.
*/
public function __construct()
{
$this->csrfToken = new CsrfToken();
// Generate or get the CSRF-Token from Cookie.
$this->token = ($this->hasToken() === false) ? $this->generateToken() : $this->csrfToken->getToken();
$this->tokenProvider = new CookieTokenProvider();
}
/**
@@ -28,23 +28,23 @@ class BaseCsrfVerifier implements IMiddleware
* @param Request $request
* @return bool
*/
protected function skip(Request $request)
protected function skip(Request $request): bool
{
if ($this->except === null || is_array($this->except) === false) {
if ($this->except === null || \count($this->except) === 0) {
return false;
}
$max = count($this->except) - 1;
$max = \count($this->except) - 1;
for ($i = $max; $i >= 0; $i--) {
$url = $this->except[$i];
$url = rtrim($url, '/');
if ($url[strlen($url) - 1] === '*') {
if ($url[\strlen($url) - 1] === '*') {
$url = rtrim($url, '*');
$skip = (stripos($request->getUri(), $url) === 0);
$skip = $request->getUrl()->contains($url);
} else {
$skip = ($url === rtrim($request->getUri(), '/'));
$skip = ($url === $request->getUrl()->getOriginalUrl());
}
if ($skip === true) {
@@ -55,46 +55,44 @@ class BaseCsrfVerifier implements IMiddleware
return false;
}
public function handle(Request $request, ILoadableRoute &$route = null)
/**
* Handle request
*
* @param Request $request
* @throws TokenMismatchException
*/
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'], false) === true) {
$token = $request->getInputHandler()->value(
static::POST_KEY,
$request->getHeader(static::HEADER_KEY),
Request::$requestTypesPost
);
$token = $request->getInput()->get(static::POST_KEY, null, 'post');
// If the token is not posted, check headers for valid x-csrf-token
if ($token === null) {
$token = $request->getHeader(static::HEADER_KEY);
}
if ($this->csrfToken->validate($token) === false) {
throw new TokenMismatchException('Invalid csrf-token.');
if ($this->tokenProvider->validate((string)$token) === false) {
throw new TokenMismatchException('Invalid CSRF-token.');
}
}
// Refresh existing token
$this->tokenProvider->refresh();
}
public function generateToken()
public function getTokenProvider(): ITokenProvider
{
$token = CsrfToken::generateToken();
$this->csrfToken->setToken($token);
return $token;
return $this->tokenProvider;
}
public function hasToken()
/**
* Set token provider
* @param ITokenProvider $provider
*/
public function setTokenProvider(ITokenProvider $provider): void
{
if ($this->token !== null) {
return true;
}
return $this->csrfToken->hasToken();
}
public function getToken()
{
return $this->token;
$this->tokenProvider = $provider;
}
}
@@ -1,4 +1,5 @@
<?php
namespace Pecee\Http\Middleware\Exceptions;
class TokenMismatchException extends \Exception
+2 -4
View File
@@ -1,16 +1,14 @@
<?php
namespace Pecee\Http\Middleware;
use Pecee\Http\Request;
use Pecee\SimpleRouter\Route\ILoadableRoute;
interface IMiddleware
{
/**
* @param Request $request
* @param ILoadableRoute $route
* @return Request|null
*/
public function handle(Request $request, ILoadableRoute &$route);
public function handle(Request $request): void;
}
+401 -80
View File
@@ -1,68 +1,175 @@
<?php
namespace Pecee\Http;
use Pecee\Http\Input\Input;
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
{
protected $data = [];
protected $headers;
protected $host;
protected $uri;
protected $method;
protected $input;
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
*
* @var array
*/
private $data = [];
/**
* Server headers
* @var array
*/
protected $headers = [];
/**
* Request ContentType
* @var string
*/
protected $contentType;
/**
* Request host
* @var string
*/
protected $host;
/**
* Current request url
* @var Url
*/
protected $url;
/**
* Request method
* @var string
*/
protected $method;
/**
* Input handler
* @var InputHandler
*/
protected $inputHandler;
/**
* Defines if request has pending rewrite
* @var bool
*/
protected $hasPendingRewrite = false;
/**
* @var ILoadableRoute|null
*/
protected $rewriteRoute;
/**
* Rewrite url
* @var string|null
*/
protected $rewriteUrl;
/**
* @var array
*/
protected $loadedRoutes = [];
/**
* Request constructor.
* @throws MalformedUrlException
*/
public function __construct()
{
$this->parseHeaders();
$this->host = $this->getHeader('http-host');
$this->uri = $this->getHeader('request-uri');
$this->input = new Input($this);
$this->method = strtolower($this->input->get('_method', $this->getHeader('request-method'), 'post'));
}
protected function parseHeaders()
{
$this->headers = [];
$max = count($_SERVER) - 1;
$keys = array_keys($_SERVER);
for ($i = $max; $i >= 0; $i--) {
$key = $keys[$i];
$value = $_SERVER[$key];
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->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);
}
public function isSecure()
public function isSecure(): bool
{
return $this->getHeader('http-x-forwarded-proto') === 'https' || $this->getHeader('https') !== null || $this->getHeader('server-port') === 443;
}
/**
* @return string
* @return Url
*/
public function getUri()
public function getUrl(): Url
{
return $this->uri;
return $this->url;
}
/**
* @return string
* Copy url object
*
* @return Url
*/
public function getHost()
public function getUrlCopy(): Url
{
return clone $this->url;
}
/**
* @return string|null
*/
public function getHost(): ?string
{
return $this->host;
}
/**
* @return string
* @return string|null
*/
public function getMethod()
public function getMethod(): ?string
{
return $this->method;
}
@@ -71,7 +178,7 @@ class Request
* Get http basic auth user
* @return string|null
*/
public function getUser()
public function getUser(): ?string
{
return $this->getHeader('php-auth-user');
}
@@ -80,51 +187,75 @@ class Request
* Get http basic auth password
* @return string|null
*/
public function getPassword()
public function getPassword(): ?string
{
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
*/
public function getHeaders()
public function getHeaders(): array
{
return $this->headers;
}
/**
* Get id address
* @return string
* 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()
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->getFirstHeader($headers);
}
return $this->getHeader('remote-addr');
/**
* Get remote address/ip
*
* @alias static::getIp
* @return string|null
*/
public function getRemoteAddr(): ?string
{
return $this->getIp();
}
/**
* Get referer
* @return string
* @return string|null
*/
public function getReferer()
public function getReferer(): ?string
{
return $this->getHeader('http-referer');
}
/**
* Get user agent
* @return string
* @return string|null
*/
public function getUserAgent()
public function getUserAgent(): ?string
{
return $this->getHeader('http-user-agent');
}
@@ -132,27 +263,43 @@ 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)
public function getHeader(string $name, $defaultValue = null, $tryParse = true): ?string
{
if (isset($this->headers[strtolower($name)])) {
return $this->headers[strtolower($name)];
$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;
}
}
$max = count($_SERVER) - 1;
$keys = array_keys($_SERVER);
return $header ?? $defaultValue;
}
for ($i = $max; $i >= 0; $i--) {
$key = $keys[$i];
$name = $_SERVER[$key];
if ($key === $name) {
return $name;
/**
* 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;
}
}
@@ -160,12 +307,37 @@ class Request
}
/**
* Get input class
* @return Input
* Get request content-type
* @return string|null
*/
public function getInput()
public function getContentType(): ?string
{
return $this->input;
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;
}
/**
* Get input class
* @return InputHandler
*/
public function getInputHandler(): InputHandler
{
return $this->inputHandler;
}
/**
@@ -175,32 +347,56 @@ class Request
*
* @return bool
*/
public function isFormatAccepted($format)
public function isFormatAccepted(string $format): bool
{
return ($this->getHeader('http-accept') !== null && stripos($this->getHeader('http-accept'), $format) > -1);
return ($this->getHeader('http-accept') !== null && stripos($this->getHeader('http-accept'), $format) !== false);
}
/**
* Returns true if the request is made through Ajax
*
* @return bool
*/
public function isAjax(): bool
{
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
*/
public function getAcceptFormats()
public function getAcceptFormats(): array
{
return explode(',', $this->getHeader('http-accept'));
}
/**
* @param string $uri
* @param Url $url
*/
public function setUri($uri)
public function setUrl(Url $url): void
{
$this->uri = $uri;
$this->url = $url;
if ($this->url->getHost() === null) {
$this->url->setHost((string)$this->getHost());
}
}
/**
* @param string $host
* @param string|null $host
*/
public function setHost($host)
public function setHost(?string $host): void
{
$this->host = $host;
}
@@ -208,14 +404,139 @@ class Request
/**
* @param string $method
*/
public function setMethod($method)
public function setMethod(string $method): void
{
$this->method = $method;
$this->method = strtolower($method);
}
public function __isset($name)
/**
* Set rewrite route
*
* @param ILoadableRoute $route
* @return static
*/
public function setRewriteRoute(ILoadableRoute $route): self
{
return array_key_exists($name, $this->data);
$this->hasPendingRewrite = true;
$this->rewriteRoute = SimpleRouter::addDefaultNamespace($route);
return $this;
}
/**
* Get rewrite route
*
* @return ILoadableRoute|null
*/
public function getRewriteRoute(): ?ILoadableRoute
{
return $this->rewriteRoute;
}
/**
* Get rewrite url
*
* @return string|null
*/
public function getRewriteUrl(): ?string
{
return $this->rewriteUrl;
}
/**
* Set rewrite url
*
* @param string $rewriteUrl
* @return static
*/
public function setRewriteUrl(string $rewriteUrl): self
{
$this->hasPendingRewrite = true;
$this->rewriteUrl = rtrim($rewriteUrl, '/') . '/';
return $this;
}
/**
* Set rewrite callback
* @param string|\Closure $callback
* @return static
*/
public function setRewriteCallback($callback): self
{
$this->hasPendingRewrite = true;
return $this->setRewriteRoute(new RouteUrl($this->getUrl()->getPath(), $callback));
}
/**
* Get loaded route
* @return ILoadableRoute|null
*/
public function getLoadedRoute(): ?ILoadableRoute
{
return (\count($this->loadedRoutes) > 0) ? end($this->loadedRoutes) : null;
}
/**
* Get all loaded routes
*
* @return array
*/
public function getLoadedRoutes(): array
{
return $this->loadedRoutes;
}
/**
* Set loaded routes
*
* @param array $routes
* @return static
*/
public function setLoadedRoutes(array $routes): self
{
$this->loadedRoutes = $routes;
return $this;
}
/**
* Added loaded route
*
* @param ILoadableRoute $route
* @return static
*/
public function addLoadedRoute(ILoadableRoute $route): self
{
$this->loadedRoutes[] = $route;
return $this;
}
/**
* Returns true if the request contains a rewrite
*
* @return bool
*/
public function hasPendingRewrite(): bool
{
return $this->hasPendingRewrite;
}
/**
* Defines if the current request contains a rewrite.
*
* @param bool $boolean
* @return Request
*/
public function setHasPendingRewrite(bool $boolean): self
{
$this->hasPendingRewrite = $boolean;
return $this;
}
public function __isset($name): bool
{
return array_key_exists($name, $this->data) === true;
}
public function __set($name, $value = null)
@@ -225,7 +546,7 @@ class Request
public function __get($name)
{
return isset($this->data[$name]) ? $this->data[$name] : null;
return $this->data[$name] ?? null;
}
}
+30 -21
View File
@@ -1,6 +1,9 @@
<?php
namespace Pecee\Http;
use Pecee\Exceptions\InvalidArgumentException;
class Response
{
protected $request;
@@ -16,7 +19,7 @@ class Response
* @param int $code
* @return static
*/
public function httpCode($code)
public function httpCode(int $code): self
{
http_response_code($code);
@@ -27,21 +30,21 @@ class Response
* Redirect the response
*
* @param string $url
* @param int $httpCode
* @param ?int $httpCode
*/
public function redirect($url, $httpCode = null)
public function redirect(string $url, ?int $httpCode = null): void
{
if ($httpCode !== null) {
$this->httpCode($httpCode);
}
$this->header('location: ' . $url);
die();
exit(0);
}
public function refresh()
public function refresh(): void
{
$this->redirect($this->request->getUri());
$this->redirect($this->request->getUrl()->getOriginalUrl());
}
/**
@@ -49,7 +52,7 @@ class Response
* @param string $name
* @return static
*/
public function auth($name = '')
public function auth(string $name = ''): self
{
$this->headers([
'WWW-Authenticate: Basic realm="' . $name . '"',
@@ -59,37 +62,43 @@ class Response
return $this;
}
public function cache($eTag, $lastModified = 2592000)
public function cache(string $eTag, int $lastModifiedTime = 2592000): self
{
$this->headers([
'Cache-Control: public',
'Last-Modified: ' . gmdate('D, d M Y H:i:s', $lastModified) . ' GMT',
'Etag: ' . $eTag,
sprintf('Last-Modified: %s GMT', gmdate('D, d M Y H:i:s', $lastModifiedTime)),
sprintf('Etag: %s', $eTag),
]);
$httpModified = $this->request->getHeader('http-if-modified-since');
$httpIfNoneMatch = $this->request->getHeader('http-if-none-match');
if (($httpIfNoneMatch !== null && $httpIfNoneMatch === $eTag) || ($httpModified !== null && strtotime($httpModified) === $lastModified)) {
if (($httpIfNoneMatch !== null && $httpIfNoneMatch === $eTag) || ($httpModified !== null && strtotime($httpModified) === $lastModifiedTime)) {
$this->header('HTTP/1.1 304 Not Modified');
exit();
exit(0);
}
return $this;
}
/**
* Json encode array
* @param array $value
* Json encode
* @param array|\JsonSerializable $value
* @param ?int $options JSON options Bitmask consisting of JSON_HEX_QUOT, JSON_HEX_TAG, JSON_HEX_AMP, JSON_HEX_APOS, JSON_NUMERIC_CHECK, JSON_PRETTY_PRINT, JSON_UNESCAPED_SLASHES, JSON_FORCE_OBJECT, JSON_PRESERVE_ZERO_FRACTION, JSON_UNESCAPED_UNICODE, JSON_PARTIAL_OUTPUT_ON_ERROR.
* @param int $dept JSON debt.
* @throws InvalidArgumentException
*/
public function json(array $value)
public function json($value, ?int $options = null, int $dept = 512): void
{
$this->header('Content-Type: application/json');
echo json_encode($value);
die();
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.');
}
$this->header('Content-Type: application/json; charset=utf-8');
echo json_encode($value, $options, $dept);
exit(0);
}
/**
@@ -97,7 +106,7 @@ class Response
* @param string $value
* @return static
*/
public function header($value)
public function header(string $value): self
{
header($value);
@@ -109,7 +118,7 @@ class Response
* @param array $headers
* @return static
*/
public function headers(array $headers)
public function headers(array $headers): self
{
foreach ($headers as $header) {
$this->header($header);
@@ -0,0 +1,116 @@
<?php
namespace Pecee\Http\Security;
use Pecee\Http\Security\Exceptions\SecurityException;
class CookieTokenProvider implements ITokenProvider
{
public const CSRF_KEY = 'CSRF-TOKEN';
protected $token;
protected $cookieTimeoutMinutes = 120;
/**
* CookieTokenProvider constructor.
* @throws SecurityException
*/
public function __construct()
{
$this->token = ($this->hasToken() === true) ? $_COOKIE[static::CSRF_KEY] : null;
if ($this->token === null) {
$this->token = $this->generateToken();
}
}
/**
* Generate random identifier for CSRF token
*
* @return string
* @throws SecurityException
*/
public function generateToken(): string
{
try {
return bin2hex(random_bytes(32));
} catch (\Exception $e) {
throw new SecurityException($e->getMessage(), (int)$e->getCode(), $e->getPrevious());
}
}
/**
* Validate valid CSRF token
*
* @param string $token
* @return bool
*/
public function validate(string $token): bool
{
if ($this->getToken() !== null) {
return hash_equals($token, $this->getToken());
}
return false;
}
/**
* Set csrf token cookie
* Overwrite this method to save the token to another storage like session etc.
*
* @param string $token
*/
public function setToken(string $token): void
{
$this->token = $token;
setcookie(static::CSRF_KEY, $token, time() + (60 * $this->cookieTimeoutMinutes), '/', ini_get('session.cookie_domain'), ini_get('session.cookie_secure'), ini_get('session.cookie_httponly'));
}
/**
* Get csrf token
* @param string|null $defaultValue
* @return string|null
*/
public function getToken(?string $defaultValue = null): ?string
{
return $this->token ?? $defaultValue;
}
/**
* Refresh existing token
*/
public function refresh(): void
{
if ($this->token !== null) {
$this->setToken($this->token);
}
}
/**
* Returns whether the csrf token has been defined
* @return bool
*/
public function hasToken(): bool
{
return isset($_COOKIE[static::CSRF_KEY]);
}
/**
* Get timeout for cookie in minutes
* @return int
*/
public function getCookieTimeoutMinutes(): int
{
return $this->cookieTimeoutMinutes;
}
/**
* Set cookie timeout in minutes
* @param int $minutes
*/
public function setCookieTimeoutMinutes(int $minutes): void
{
$this->cookieTimeoutMinutes = $minutes;
}
}
@@ -0,0 +1,8 @@
<?php
namespace Pecee\Http\Security\Exceptions;
class SecurityException extends \Exception
{
}
@@ -0,0 +1,29 @@
<?php
namespace Pecee\Http\Security;
interface ITokenProvider
{
/**
* Refresh existing token
*/
public function refresh(): void;
/**
* Validate valid CSRF token
*
* @param string $token
* @return bool
*/
public function validate(string $token): bool;
/**
* Get token token
*
* @param string|null $defaultValue
* @return string|null
*/
public function getToken(?string $defaultValue = null): ?string;
}
+476
View File
@@ -0,0 +1,476 @@
<?php
namespace Pecee\Http;
use Pecee\Http\Exceptions\MalformedUrlException;
class Url implements \JsonSerializable
{
private $originalUrl;
private $scheme;
private $host;
private $port;
private $username;
private $password;
private $path;
private $params = [];
private $fragment;
/**
* Url constructor.
*
* @param ?string $url
* @throws MalformedUrlException
*/
public function __construct(?string $url)
{
$this->originalUrl = $url;
if ($url !== null && $url !== '/') {
$data = $this->parseUrl($url);
$this->scheme = $data['scheme'] ?? null;
$this->host = $data['host'] ?? null;
$this->port = $data['port'] ?? null;
$this->username = $data['user'] ?? null;
$this->password = $data['pass'] ?? null;
if (isset($data['path']) === true) {
$this->setPath($data['path']);
}
$this->fragment = $data['fragment'] ?? null;
if (isset($data['query']) === true) {
$this->setQueryString($data['query']);
}
}
}
/**
* Check if url is using a secure protocol like https
*
* @return bool
*/
public function isSecure(): bool
{
return (strtolower($this->getScheme()) === 'https');
}
/**
* Checks if url is relative
*
* @return bool
*/
public function isRelative(): bool
{
return ($this->getHost() === null);
}
/**
* Get url scheme
*
* @return string|null
*/
public function getScheme(): ?string
{
return $this->scheme;
}
/**
* Set the scheme of the url
*
* @param string $scheme
* @return static
*/
public function setScheme(string $scheme): self
{
$this->scheme = $scheme;
return $this;
}
/**
* Get url host
*
* @return string|null
*/
public function getHost(): ?string
{
return $this->host;
}
/**
* Set the host of the url
*
* @param string $host
* @return static
*/
public function setHost(string $host): self
{
$this->host = $host;
return $this;
}
/**
* Get url port
*
* @return int|null
*/
public function getPort(): ?int
{
return ($this->port !== null) ? (int)$this->port : null;
}
/**
* Set the port of the url
*
* @param int $port
* @return static
*/
public function setPort(int $port): self
{
$this->port = $port;
return $this;
}
/**
* Parse username from url
*
* @return string|null
*/
public function getUsername(): ?string
{
return $this->username;
}
/**
* Set the username of the url
*
* @param string $username
* @return static
*/
public function setUsername(string $username): self
{
$this->username = $username;
return $this;
}
/**
* Parse password from url
* @return string|null
*/
public function getPassword(): ?string
{
return $this->password;
}
/**
* Set the url password
*
* @param string $password
* @return static
*/
public function setPassword(string $password): self
{
$this->password = $password;
return $this;
}
/**
* Get path from url
* @return string
*/
public function getPath(): ?string
{
return $this->path ?? '/';
}
/**
* Set the url path
*
* @param string $path
* @return static
*/
public function setPath(string $path): self
{
$this->path = rtrim($path, '/') . '/';
return $this;
}
/**
* Get query-string from url
*
* @return array
*/
public function getParams(): array
{
return $this->params;
}
/**
* Merge parameters array
*
* @param array $params
* @return static
*/
public function mergeParams(array $params): self
{
return $this->setParams(array_merge($this->getParams(), $params));
}
/**
* Set the url params
*
* @param array $params
* @return static
*/
public function setParams(array $params): self
{
$this->params = $params;
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
*
* @return string
*/
public function getQueryString(): string
{
return static::arrayToParams($this->getParams());
}
/**
* Get fragment from url (everything after #)
*
* @return string|null
*/
public function getFragment(): ?string
{
return $this->fragment;
}
/**
* Set url fragment
*
* @param string $fragment
* @return static
*/
public function setFragment(string $fragment): self
{
$this->fragment = $fragment;
return $this;
}
/**
* @return string
*/
public function getOriginalUrl(): string
{
return $this->originalUrl;
}
/**
* Get position of value.
* Returns -1 on failure.
*
* @param string $value
* @return int
*/
public function indexOf(string $value): int
{
$index = stripos($this->getOriginalUrl(), $value);
return ($index === false) ? -1 : $index;
}
/**
* Check if url contains value.
*
* @param string $value
* @return bool
*/
public function contains(string $value): bool
{
return (stripos($this->getOriginalUrl(), $value) !== false);
}
/**
* Check if url contains parameter/query string.
*
* @param string $name
* @return bool
*/
public function hasParam(string $name): bool
{
return array_key_exists($name, $this->getParams());
}
/**
* 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): self
{
$params = $this->getParams();
unset($params[$name]);
$this->setParams($params);
return $this;
}
/**
* Get parameter by name.
* Returns parameter value or default value.
*
* @param string $name
* @param string|null $defaultValue
* @return string|null
*/
public function getParam(string $name, ?string $defaultValue = null): ?string
{
return (isset($this->getParams()[$name]) === true) ? $this->getParams()[$name] : $defaultValue;
}
/**
* UTF-8 aware parse_url() replacement.
* @param string $url
* @param int $component
* @return array
* @throws MalformedUrlException
*/
public function parseUrl(string $url, int $component = -1): array
{
$encodedUrl = preg_replace_callback(
'/[^:\/@?&=#]+/u',
static function ($matches) {
return urlencode($matches[0]);
},
$url
);
$parts = parse_url($encodedUrl, $component);
if ($parts === false) {
throw new MalformedUrlException(sprintf('Failed to parse url: "%s"', $url));
}
return array_map('urldecode', $parts);
}
/**
* Convert array to query-string params
*
* @param array $getParams
* @param bool $includeEmpty
* @return string
*/
public static function arrayToParams(array $getParams = [], bool $includeEmpty = true): string
{
if (\count($getParams) !== 0) {
if ($includeEmpty === false) {
$getParams = array_filter($getParams, static function ($item) {
return (trim($item) !== '');
});
}
return http_build_query($getParams);
}
return '';
}
/**
* Returns the relative url
*
* @return string
*/
public function getRelativeUrl(): string
{
$params = $this->getQueryString();
$path = $this->path ?? '';
$query = $params !== '' ? '?' . $params : '';
$fragment = $this->fragment !== null ? '#' . $this->fragment : '';
return $path . $query . $fragment;
}
/**
* Returns the absolute url
*
* @return string
*/
public function getAbsoluteUrl(): string
{
$scheme = $this->scheme !== null ? $this->scheme . '://' : '';
$host = $this->host ?? '';
$port = $this->port !== null ? ':' . $this->port : '';
$user = $this->username ?? '';
$pass = $this->password !== null ? ':' . $this->password : '';
$pass = ($user || $pass) ? $pass . '@' : '';
return $scheme . $user . $pass . $host . $port . $this->getRelativeUrl();
}
/**
* 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();
}
}
@@ -0,0 +1,37 @@
<?php
namespace Pecee\SimpleRouter\ClassLoader;
use Pecee\SimpleRouter\Exceptions\ClassNotFoundHttpException;
class ClassLoader implements IClassLoader
{
/**
* Load class
*
* @param string $class
* @return object
* @throws ClassNotFoundHttpException
*/
public function loadClass(string $class)
{
if (class_exists($class) === false) {
throw new ClassNotFoundHttpException($class, null, sprintf('Class "%s" does not exist', $class), 404, null);
}
return new $class();
}
/**
* Load closure
*
* @param Callable $closure
* @param array $parameters
* @return mixed
*/
public function loadClosure(Callable $closure, array $parameters)
{
return \call_user_func_array($closure, $parameters);
}
}
@@ -0,0 +1,24 @@
<?php
namespace Pecee\SimpleRouter\ClassLoader;
interface IClassLoader
{
/**
* Called when loading class
* @param string $class
* @return object
*/
public function loadClass(string $class);
/**
* Called when loading method
*
* @param callable $closure
* @param array $parameters
* @return mixed
*/
public function loadClosure(Callable $closure, array $parameters);
}
@@ -0,0 +1,111 @@
<?php
namespace Pecee\SimpleRouter\Event;
use Pecee\Http\Request;
use Pecee\SimpleRouter\Router;
class EventArgument implements IEventArgument
{
/**
* Event name
* @var string
*/
protected $eventName;
/**
* @var Router
*/
protected $router;
/**
* @var array
*/
protected $arguments = [];
public function __construct($eventName, $router, array $arguments = [])
{
$this->eventName = $eventName;
$this->router = $router;
$this->arguments = $arguments;
}
/**
* Get event name
*
* @return string
*/
public function getEventName(): string
{
return $this->eventName;
}
/**
* Set the event name
*
* @param string $name
*/
public function setEventName(string $name): void
{
$this->eventName = $name;
}
/**
* Get the router instance
*
* @return Router
*/
public function getRouter(): Router
{
return $this->router;
}
/**
* Get the request instance
*
* @return Request
*/
public function getRequest(): Request
{
return $this->getRouter()->getRequest();
}
/**
* @param string $name
* @return mixed
*/
public function __get(string $name)
{
return $this->arguments[$name] ?? null;
}
/**
* @param string $name
* @return bool
*/
public function __isset(string $name): bool
{
return array_key_exists($name, $this->arguments);
}
/**
* @param string $name
* @param mixed $value
* @throws \InvalidArgumentException
*/
public function __set(string $name, $value)
{
throw new \InvalidArgumentException('Not supported');
}
/**
* Get arguments
*
* @return array
*/
public function getArguments(): array
{
return $this->arguments;
}
}
@@ -0,0 +1,46 @@
<?php
namespace Pecee\SimpleRouter\Event;
use Pecee\Http\Request;
use Pecee\SimpleRouter\Router;
interface IEventArgument
{
/**
* Get event name
*
* @return string
*/
public function getEventName(): string;
/**
* Set event name
*
* @param string $name
*/
public function setEventName(string $name): void;
/**
* Get router instance
*
* @return Router
*/
public function getRouter(): Router;
/**
* Get request instance
*
* @return Request
*/
public function getRequest(): Request;
/**
* Get all event arguments
*
* @return array
*/
public function getArguments(): array;
}
@@ -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;
}
}
@@ -1,4 +1,5 @@
<?php
namespace Pecee\SimpleRouter\Exceptions;
class HttpException extends \Exception
@@ -1,6 +1,8 @@
<?php
namespace Pecee\SimpleRouter\Exceptions;
class NotFoundHttpException extends HttpException
{
}
@@ -0,0 +1,37 @@
<?php
namespace Pecee\SimpleRouter\Handlers;
use Pecee\Http\Request;
/**
* Class CallbackExceptionHandler
*
* Class is used to create callbacks which are fired when an exception is reached.
* This allows for easy handling 404-exception etc. without creating an custom ExceptionHandler.
*
* @package \Pecee\SimpleRouter\Handlers
*/
class CallbackExceptionHandler implements IExceptionHandler
{
protected $callback;
public function __construct(\Closure $callback)
{
$this->callback = $callback;
}
/**
* @param Request $request
* @param \Exception $error
*/
public function handleError(Request $request, \Exception $error): void
{
/* Fire exceptions */
\call_user_func($this->callback,
$request,
$error
);
}
}
@@ -0,0 +1,62 @@
<?php
namespace Pecee\SimpleRouter\Handlers;
use Pecee\SimpleRouter\Event\EventArgument;
use Pecee\SimpleRouter\Router;
class DebugEventHandler implements IEventHandler
{
/**
* Debug callback
* @var \Closure
*/
protected $callback;
public function __construct()
{
$this->callback = static function (EventArgument $argument) {
// todo: log in database
};
}
/**
* Get events.
*
* @param string|null $name Filter events by name.
* @return array
*/
public function getEvents(?string $name): array
{
return [
$name => [
$this->callback,
],
];
}
/**
* Fires any events registered with given event-name
*
* @param Router $router Router instance
* @param string $name Event name
* @param array $eventArgs Event arguments
*/
public function fireEvents(Router $router, string $name, array $eventArgs = []): void
{
$callback = $this->callback;
$callback(new EventArgument($router, $eventArgs));
}
/**
* Set debug callback
*
* @param \Closure $event
*/
public function setCallback(\Closure $event): void
{
$this->callback = $event;
}
}
@@ -0,0 +1,184 @@
<?php
namespace Pecee\SimpleRouter\Handlers;
use Pecee\SimpleRouter\Event\EventArgument;
use Pecee\SimpleRouter\Router;
class EventHandler implements IEventHandler
{
/**
* Fires when a event is triggered.
*/
public const EVENT_ALL = '*';
/**
* Fires when router is initializing and before routes are loaded.
*/
public const EVENT_INIT = 'onInit';
/**
* Fires when all routes has been loaded and rendered, just before the output is returned.
*/
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.
*/
public const EVENT_REWRITE = 'onRewrite';
/**
* Fires when the router is booting.
* This happens just before boot-managers are rendered and before any routes has been loaded.
*/
public const EVENT_BOOT = 'onBoot';
/**
* Fires before a boot-manager is rendered.
*/
public const EVENT_RENDER_BOOTMANAGER = 'onRenderBootManager';
/**
* Fires when the router is about to load all routes.
*/
public const EVENT_LOAD_ROUTES = 'onLoadRoutes';
/**
* Fires whenever the `findRoute` method is called within the `Router`.
* This usually happens when the router tries to find routes that
* contains a certain url, usually after the EventHandler::EVENT_GET_URL event.
*/
public const EVENT_FIND_ROUTE = 'onFindRoute';
/**
* Fires whenever the `Router::getUrl` method or `url`-helper function
* is called and the router tries to find the route.
*/
public const EVENT_GET_URL = 'onGetUrl';
/**
* Fires when a route is matched and valid (correct request-type etc).
* and before the route is rendered.
*/
public const EVENT_MATCH_ROUTE = 'onMatchRoute';
/**
* Fires before a route is rendered.
*/
public const EVENT_RENDER_ROUTE = 'onRenderRoute';
/**
* Fires when the router is loading exception-handlers.
*/
public const EVENT_LOAD_EXCEPTIONS = 'onLoadExceptions';
/**
* Fires before the router is rendering a exception-handler.
*/
public const EVENT_RENDER_EXCEPTION = 'onRenderException';
/**
* Fires before a middleware is rendered.
*/
public const EVENT_RENDER_MIDDLEWARES = 'onRenderMiddlewares';
/**
* Fires before the CSRF-verifier is rendered.
*/
public const EVENT_RENDER_CSRF = 'onRenderCsrfVerifier';
/**
* All available events
* @var array
*/
public static $events = [
self::EVENT_ALL,
self::EVENT_INIT,
self::EVENT_LOAD,
self::EVENT_ADD_ROUTE,
self::EVENT_REWRITE,
self::EVENT_BOOT,
self::EVENT_RENDER_BOOTMANAGER,
self::EVENT_LOAD_ROUTES,
self::EVENT_FIND_ROUTE,
self::EVENT_GET_URL,
self::EVENT_MATCH_ROUTE,
self::EVENT_RENDER_ROUTE,
self::EVENT_LOAD_EXCEPTIONS,
self::EVENT_RENDER_EXCEPTION,
self::EVENT_RENDER_MIDDLEWARES,
self::EVENT_RENDER_CSRF,
];
/**
* List of all registered events
* @var array
*/
private $registeredEvents = [];
/**
* Register new event
*
* @param string $name
* @param \Closure $callback
* @return static
*/
public function register(string $name, \Closure $callback): IEventHandler
{
if (isset($this->registeredEvents[$name]) === true) {
$this->registeredEvents[$name][] = $callback;
} else {
$this->registeredEvents[$name] = [$callback];
}
return $this;
}
/**
* Get events.
*
* @param string|null $name Filter events by name.
* @param array|string ...$names Add multiple names...
* @return array
*/
public function getEvents(?string $name, ...$names): array
{
if ($name === null) {
return $this->registeredEvents;
}
$names[] = $name;
$events = [];
foreach ($names as $eventName) {
if (isset($this->registeredEvents[$eventName]) === true) {
$events += $this->registeredEvents[$eventName];
}
}
return $events;
}
/**
* Fires any events registered with given event-name
*
* @param Router $router Router instance
* @param string $name Event name
* @param array $eventArgs Event arguments
*/
public function fireEvents(Router $router, string $name, array $eventArgs = []): void
{
$events = $this->getEvents(static::EVENT_ALL, $name);
/* @var $event \Closure */
foreach ($events as $event) {
$event(new EventArgument($name, $router, $eventArgs));
}
}
}
@@ -0,0 +1,27 @@
<?php
namespace Pecee\SimpleRouter\Handlers;
use Pecee\SimpleRouter\Router;
interface IEventHandler
{
/**
* Get events.
*
* @param string|null $name Filter events by name.
* @return array
*/
public function getEvents(?string $name): array;
/**
* Fires any events registered with given event-name
*
* @param Router $router Router instance
* @param string $name Event name
* @param array $eventArgs Event arguments
*/
public function fireEvents(Router $router, string $name, array $eventArgs = []): void;
}
@@ -0,0 +1,15 @@
<?php
namespace Pecee\SimpleRouter\Handlers;
use Pecee\Http\Request;
interface IExceptionHandler
{
/**
* @param Request $request
* @param \Exception $error
*/
public function handleError(Request $request, \Exception $error): void;
}
@@ -1,4 +1,5 @@
<?php
namespace Pecee\SimpleRouter;
use Pecee\Http\Request;
@@ -8,8 +9,8 @@ interface IRouterBootManager
/**
* Called when router loads it's routes
*
* @param Router $router
* @param Request $request
* @return Request
*/
public function boot(Request $request);
public function boot(Router $router, Request $request): void;
}
@@ -1,4 +1,5 @@
<?php
namespace Pecee\SimpleRouter\Route;
interface IControllerRoute extends IRoute
@@ -8,7 +9,7 @@ interface IControllerRoute extends IRoute
*
* @return string
*/
public function getController();
public function getController(): string;
/**
* Set controller class-name
@@ -16,21 +17,6 @@ interface IControllerRoute extends IRoute
* @param string $controller
* @return static
*/
public function setController($controller);
/**
* Return active method
*
* @return string
*/
public function getMethod();
/**
* Set active method
*
* @param string $method
* @return static
*/
public function setMethod($method);
public function setController(string $controller): self;
}
+21 -11
View File
@@ -1,7 +1,9 @@
<?php
namespace Pecee\SimpleRouter\Route;
use Pecee\Http\Request;
use Pecee\SimpleRouter\Handlers\IExceptionHandler;
interface IGroupRoute extends IRoute
{
@@ -11,50 +13,58 @@ interface IGroupRoute extends IRoute
* @param Request $request
* @return bool
*/
public function matchDomain(Request $request);
public function matchDomain(Request $request): bool;
/**
* Add exception handler
*
* @param IExceptionHandler|string $handler
* @return static
*/
public function addExceptionHandler($handler): self;
/**
* Set exception-handlers for group
*
* @param array $handlers
* @return static $this
* @return static
*/
public function setExceptionHandlers(array $handlers);
public function setExceptionHandlers(array $handlers): self;
/**
* Get exception-handlers for group
*
* @return array
*/
public function getExceptionHandlers();
public function getExceptionHandlers(): array;
/**
* Get domains for domain.
*
* @return array
*/
public function getDomains();
public function getDomains(): array;
/**
* Set allowed domains for group.
*
* @param array $domains
* @return $this
* @return static
*/
public function setDomains(array $domains);
public function setDomains(array $domains): self;
/**
* Set prefix that child-routes will inherit.
*
* @param string $prefix
* @return string
* @return static
*/
public function setPrefix($prefix);
public function setPrefix(string $prefix): self;
/**
* Get prefix.
*
* @return string
* @return string|null
*/
public function getPrefix();
public function getPrefix(): ?string;
}
+32 -14
View File
@@ -1,7 +1,9 @@
<?php
namespace Pecee\SimpleRouter\Route;
use Pecee\Http\Request;
use Pecee\SimpleRouter\Router;
interface ILoadableRoute extends IRoute
{
@@ -10,30 +12,46 @@ interface ILoadableRoute extends IRoute
* Used when calling the url() helper.
*
* @param string|null $method
* @param array|null $parameters
* @param array|string|null $parameters
* @param string|null $name
* @return string
*/
public function findUrl($method = null, $parameters = null, $name = null);
public function findUrl(?string $method = null, $parameters = null, ?string $name = null): string;
/**
* Loads and renders middlewares-classes
* Loads and renders middleware-classes
*
* @param Request $request
* @param ILoadableRoute $route
* @param Router $router
*/
public function loadMiddleware(Request $request, ILoadableRoute $route);
public function loadMiddleware(Request $request, Router $router): void;
public function getUrl();
/**
* Get url
* @return string
*/
public function getUrl(): string;
public function setUrl($url);
/**
* Set url
* @param string $url
* @return static
*/
public function setUrl(string $url): self;
/**
* Prepend url
* @param string $url
* @return ILoadableRoute
*/
public function prependUrl(string $url): self;
/**
* Returns the provided name for the router.
*
* @return string
* @return string|null
*/
public function getName();
public function getName(): ?string;
/**
* Check if route has given name.
@@ -41,22 +59,22 @@ interface ILoadableRoute extends IRoute
* @param string $name
* @return bool
*/
public function hasName($name);
public function hasName(string $name): bool;
/**
* Sets the router name, which makes it easier to obtain the url or router at a later point.
*
* @param string $name
* @return static $this
* @return static
*/
public function setName($name);
public function setName(string $name): self;
/**
* Get regular expression match used for matching route (if defined).
*
* @return string
*/
public function getMatch();
public function getMatch(): ?string;
/**
* Add regular expression match for the entire route.
@@ -64,6 +82,6 @@ interface ILoadableRoute extends IRoute
* @param string $regex
* @return static
*/
public function setMatch($regex);
public function setMatch(string $regex): self;
}
@@ -0,0 +1,8 @@
<?php
namespace Pecee\SimpleRouter\Route;
interface IPartialGroupRoute
{
}
+76 -38
View File
@@ -1,27 +1,31 @@
<?php
namespace Pecee\SimpleRouter\Route;
use Pecee\Http\Request;
use Pecee\SimpleRouter\Router;
interface IRoute
{
/**
* Method called to check if a domain matches
*
* @param string $url
* @param Request $request
* @return bool
*/
public function matchRoute(Request $request);
public function matchRoute(string $url, Request $request): bool;
/**
* Called when route is matched.
* Returns class to be rendered.
*
* @param Request $request
* @param Router $router
* @return string
* @throws \Pecee\SimpleRouter\Exceptions\NotFoundHttpException
* @return void
*/
public function renderRoute(Request $request);
public function renderRoute(Request $request, Router $router): ?string;
/**
* Returns callback name/identifier for the current route based on the callback.
@@ -30,95 +34,115 @@ interface IRoute
*
* @return string
*/
public function getIdentifier();
public function getIdentifier(): string;
/**
* Set allowed request methods
*
* @param array $methods
* @return static $this
* @return static
*/
public function setRequestMethods(array $methods);
public function setRequestMethods(array $methods): self;
/**
* Get allowed request methods
*
* @return array
*/
public function getRequestMethods();
public function getRequestMethods(): array;
/**
* @return IRoute|null
*/
public function getParent();
public function getParent(): ?IRoute;
/**
* Get the group for the route.
*
* @return IGroupRoute|null
*/
public function getGroup();
public function getGroup(): ?IGroupRoute;
/**
* Set group
*
* @param IGroupRoute $group
* @return static $this
* @return static
*/
public function setGroup(IGroupRoute $group);
public function setGroup(IGroupRoute $group): self;
/**
* Set parent route
*
* @param IRoute $parent
* @return static $this
* @return static
*/
public function setParent(IRoute $parent);
public function setParent(IRoute $parent): self;
/**
* Set callback
*
* @param string $callback
* @param string|array|\Closure $callback
* @return static
*/
public function setCallback($callback);
public function setCallback($callback): self;
/**
* @return string
* @return string|callable
*/
public function getCallback();
public function getMethod();
/**
* Return active method
*
* @return string|null
*/
public function getMethod(): ?string;
public function getClass();
/**
* Set active method
*
* @param string $method
* @return static
*/
public function setMethod(string $method): self;
public function setMethod($method);
/**
* Get class
*
* @return string|null
*/
public function getClass(): ?string;
/**
* @param string $namespace
* @return static $this
* @return static
*/
public function setNamespace($namespace);
public function setNamespace(string $namespace): self;
/**
* @return string
* @return string|null
*/
public function getNamespace();
public function getNamespace(): ?string;
/**
* @param string $namespace
* @return static $this
* @return static
*/
public function setDefaultNamespace($namespace);
public function setDefaultNamespace(string $namespace): IRoute;
public function getDefaultNamespace();
/**
* Get default namespace
* @return string|null
*/
public function getDefaultNamespace(): ?string;
/**
* Get parameter names.
*
* @return array
*/
public function getWhere();
public function getWhere(): array;
/**
* Set parameter names.
@@ -126,45 +150,45 @@ interface IRoute
* @param array $options
* @return static
*/
public function setWhere(array $options);
public function setWhere(array $options): self;
/**
* Get parameters
*
* @return array
*/
public function getParameters();
public function getParameters(): array;
/**
* Get parameters
*
* @param array $parameters
* @return static $this
* @return static
*/
public function setParameters(array $parameters);
public function setParameters(array $parameters): self;
/**
* Merge with information from another route.
*
* @param array $settings
* @param bool $merge
* @return static $this
* @return static
*/
public function setSettings(array $settings, $merge = false);
public function setSettings(array $settings, bool $merge = false): self;
/**
* Export route settings to array so they can be merged with another route.
*
* @return array
*/
public function toArray();
public function toArray(): array;
/**
* Get middlewares array
*
* @return array
*/
public function getMiddlewares();
public function getMiddlewares(): array;
/**
* Set middleware class-name
@@ -172,14 +196,28 @@ interface IRoute
* @param string $middleware
* @return static
*/
public function setMiddleware($middleware);
public function addMiddleware(string $middleware): self;
/**
* Set middlewares array
*
* @param array $middlewares
* @return $this
* @return static
*/
public function setMiddlewares(array $middlewares);
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;
}
+77 -72
View File
@@ -1,9 +1,11 @@
<?php
namespace Pecee\SimpleRouter\Route;
use Pecee\Http\Middleware\IMiddleware;
use Pecee\Http\Request;
use Pecee\SimpleRouter\Exceptions\HttpException;
use Pecee\SimpleRouter\Router;
abstract class LoadableRoute extends Route implements ILoadableRoute
{
@@ -23,50 +25,42 @@ abstract class LoadableRoute extends Route implements ILoadableRoute
* Loads and renders middlewares-classes
*
* @param Request $request
* @param ILoadableRoute $route
* @param Router $router
* @throws HttpException
*/
public function loadMiddleware(Request $request, ILoadableRoute $route)
public function loadMiddleware(Request $request, Router $router): void
{
if (count($this->getMiddlewares()) > 0) {
$router->debug('Loading middlewares');
$max = count($this->getMiddlewares());
foreach ($this->getMiddlewares() as $middleware) {
for ($i = 0; $i < $max; $i++) {
$middleware = $this->getMiddlewares()[$i];
$middleware = $this->loadClass($middleware);
if (!($middleware instanceof IMiddleware)) {
throw new HttpException($middleware . ' must be instance of Middleware');
}
$middleware->handle($request, $route);
if (\is_object($middleware) === false) {
$middleware = $router->getClassLoader()->loadClass($middleware);
}
if (($middleware instanceof IMiddleware) === false) {
throw new HttpException($middleware . ' must be inherit the IMiddleware interface');
}
$className = \get_class($middleware);
$router->debug('Loading middleware "%s"', $className);
$middleware->handle($request);
$router->debug('Finished loading middleware "%s"', $className);
}
$router->debug('Finished loading middlewares');
}
public function matchRegex(Request $request, $url)
public function matchRegex(Request $request, $url): ?bool
{
/* Match on custom defined regular expression */
if ($this->regex === null) {
return null;
}
$parameters = [];
if (preg_match($this->regex, $request->getHost() . $url, $parameters) > 0) {
/* Remove global match */
$this->parameters = array_slice($parameters, 1);
return true;
}
return false;
return ((bool)preg_match($this->regex, $url) !== false);
}
/**
@@ -75,15 +69,15 @@ abstract class LoadableRoute extends Route implements ILoadableRoute
* @param string $url
* @return static
*/
public function setUrl($url)
public function setUrl(string $url): ILoadableRoute
{
$this->url = ($url === '/') ? '/' : '/' . trim($url, '/') . '/';
if (strpos($this->url, $this->paramModifiers[0]) !== false) {
$regex = sprintf(static::PARAMETERS_REGEX_MATCH, $this->paramModifiers[0], $this->paramOptionalSymbol, $this->paramModifiers[1]);
$regex = sprintf(static::PARAMETERS_REGEX_FORMAT, $this->paramModifiers[0], $this->paramOptionalSymbol, $this->paramModifiers[1]);
if (preg_match_all('/' . $regex . '/is', $this->url, $matches)) {
if ((bool)preg_match_all('/' . $regex . '/u', $this->url, $matches) !== false) {
$this->parameters = array_fill_keys($matches[1], null);
}
}
@@ -91,7 +85,18 @@ abstract class LoadableRoute extends Route implements ILoadableRoute
return $this;
}
public function getUrl()
/**
* Prepend url
*
* @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;
}
@@ -101,20 +106,19 @@ abstract class LoadableRoute extends Route implements ILoadableRoute
* Used when calling the url() helper.
*
* @param string|null $method
* @param array|null $parameters
* @param string|array|null $parameters
* @param string|null $name
* @return string
*/
public function findUrl($method = null, $parameters = null, $name = null)
public function findUrl(?string $method = null, $parameters = null, ?string $name = null): string
{
$url = $this->getUrl();
if ($this->getGroup() !== null && count($this->getGroup()->getDomains()) > 0) {
$url = '//' . $this->getGroup()->getDomains()[0] . $url;
}
$group = $this->getGroup();
/* Contains parameters that aren't recognized and will be appended at the end of the url */
$unknownParams = [];
if ($group !== null && \count($group->getDomains()) !== 0) {
$url = '//' . $group->getDomains()[0] . $url;
}
/* Create the param string - {parameter} */
$param1 = $this->paramModifiers[0] . '%s' . $this->paramModifiers[1];
@@ -125,31 +129,31 @@ abstract class LoadableRoute extends Route implements ILoadableRoute
/* Replace any {parameter} in the url with the correct value */
$params = $this->getParameters();
$max = count($params) - 1;
$keys = array_keys($params);
for ($i = $max; $i >= 0; $i--) {
$param = $keys[$i];
$value = $value = ($parameters !== null && array_key_exists($param, $parameters)) ? $parameters[$param] : $params[$param];
foreach (array_keys($params) as $param) {
/* If parameter is specifically set to null - use the original-defined value */
if ($value === null && isset($this->originalParameters[$param])) {
$value = $this->originalParameters[$param];
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]) === true) {
$value = $this->originalParameters[$param];
}
}
if (stripos($url, $param1) !== false || stripos($url, $param) !== false) {
/* Add parameter to the correct position */
$url = str_ireplace([sprintf($param1, $param), sprintf($param2, $param)], $value, $url);
} else {
$unknownParams[$param] = $value;
/* Parameter aren't recognized and will be appended at the end of the url */
$url .= $value . '/';
}
}
$url .= join('/', $unknownParams);
return rtrim($url, '/') . '/';
return rtrim('/' . ltrim($url, '/'), '/') . '/';
}
/**
@@ -157,7 +161,7 @@ abstract class LoadableRoute extends Route implements ILoadableRoute
*
* @return string
*/
public function getName()
public function getName(): ?string
{
return $this->name;
}
@@ -168,9 +172,9 @@ abstract class LoadableRoute extends Route implements ILoadableRoute
* @param string $name
* @return bool
*/
public function hasName($name)
public function hasName(string $name): bool
{
return (strtolower($this->name) === strtolower($name));
return strtolower($this->name) === strtolower($name);
}
/**
@@ -179,7 +183,7 @@ abstract class LoadableRoute extends Route implements ILoadableRoute
* @param string $regex
* @return static
*/
public function setMatch($regex)
public function setMatch(string $regex): ILoadableRoute
{
$this->regex = $regex;
@@ -191,7 +195,7 @@ abstract class LoadableRoute extends Route implements ILoadableRoute
*
* @return string
*/
public function getMatch()
public function getMatch(): string
{
return $this->regex;
}
@@ -204,7 +208,7 @@ abstract class LoadableRoute extends Route implements ILoadableRoute
* @param string|array $name
* @return static
*/
public function name($name)
public function name($name): ILoadableRoute
{
return $this->setName($name);
}
@@ -213,9 +217,9 @@ abstract class LoadableRoute extends Route implements ILoadableRoute
* Sets the router name, which makes it easier to obtain the url or router at a later point.
*
* @param string $name
* @return static $this
* @return static
*/
public function setName($name)
public function setName(string $name): ILoadableRoute
{
$this->name = $name;
@@ -225,27 +229,28 @@ 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, $merge = false)
public function setSettings(array $settings, bool $merge = false): IRoute
{
if (isset($values['as'])) {
if (isset($settings['as']) === true) {
$name = $settings['as'];
if ($this->name !== null && $merge !== false) {
$this->setName($values['as'] . '.' . $this->name);
} else {
$this->setName($values['as']);
$name .= '.' . $this->name;
}
$this->setName($name);
}
if (isset($values['prefix'])) {
$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);
}
}
+242 -136
View File
@@ -1,28 +1,17 @@
<?php
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
{
const PARAMETERS_REGEX_MATCH = '%s([\w]+)(\%s?)%s';
const REQUEST_TYPE_GET = 'get';
const REQUEST_TYPE_POST = 'post';
const REQUEST_TYPE_PUT = 'put';
const REQUEST_TYPE_PATCH = 'patch';
const REQUEST_TYPE_OPTIONS = 'options';
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_REGEX_FORMAT = '%s([\w]+)(\%s?)%s';
protected const PARAMETERS_DEFAULT_REGEX = '[\w-]+';
/**
* If enabled parameters containing null-value
@@ -30,9 +19,16 @@ abstract class Route implements IRoute
*
* @var bool
*/
protected $filterEmptyParams = false;
protected $filterEmptyParams = true;
/**
* Default regular expression used for parsing parameters.
* @var string|null
*/
protected $defaultParameterRegex;
protected $paramModifiers = '{}';
protected $paramOptionalSymbol = '?';
protected $urlRegex = '/^%s\/?$/u';
protected $group;
protected $parent;
protected $callback;
@@ -46,91 +42,124 @@ abstract class Route implements IRoute
protected $originalParameters = [];
protected $middlewares = [];
protected function loadClass($name)
/**
* Render route
*
* @param Request $request
* @param Router $router
* @return string|null
* @throws NotFoundHttpException
*/
public function renderRoute(Request $request, Router $router): ?string
{
if (class_exists($name) === false) {
throw new NotFoundHttpException(sprintf('Class %s does not exist', $name), 404);
$router->debug('Starting rendering route "%s"', \get_class($this));
$callback = $this->getCallback();
if ($callback === null) {
return null;
}
return new $name();
}
$router->debug('Parsing parameters');
public function renderRoute(Request $request)
{
if ($this->getCallback() !== null && is_callable($this->getCallback())) {
$parameters = $this->getParameters();
$router->debug('Finished parsing parameters');
/* Filter parameters with null-value */
if ($this->filterEmptyParams === true) {
$parameters = array_filter($parameters, static function ($var) {
return ($var !== null);
});
}
/* Render callback function */
if (\is_callable($callback) === true) {
$router->debug('Executing callback');
/* When the callback is a function */
call_user_func_array($this->getCallback(), $this->getParameters());
} else {
/* When the callback is a method */
$controller = explode('@', $this->getCallback());
$className = $this->getNamespace() . '\\' . $controller[0];
$class = $this->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);
}
$parameters = $this->getParameters();
/* Filter parameters with null-value */
if ($this->filterEmptyParams === true) {
$parameters = array_filter($parameters, function ($var) {
return ($var !== null);
});
}
call_user_func_array([$class, $method], $parameters);
return $router->getClassLoader()->loadClosure($callback, $parameters);
}
$controller = $this->getClass();
$method = $this->getMethod();
$namespace = $this->getNamespace();
$className = ($namespace !== null && $controller[0] !== '\\') ? $namespace . '\\' . $controller : $controller;
$router->debug('Loading class %s', $className);
$class = $router->getClassLoader()->loadClass($className);
if ($method === null) {
$controller[1] = '__invoke';
}
if (method_exists($class, $method) === false) {
throw new ClassNotFoundHttpException($className, $method, sprintf('Method "%s" does not exist in class "%s"', $method, $className), 404, null);
}
$router->debug('Executing callback');
return \call_user_func_array([$class, $method], $parameters);
}
protected function parseParameters($route, $url, $parameterRegex = '[\w]+')
protected function parseParameters($route, $url, $parameterRegex = null): ?array
{
$regex = sprintf(static::PARAMETERS_REGEX_MATCH, $this->paramModifiers[0], $this->paramOptionalSymbol, $this->paramModifiers[1]);
$regex = (strpos($route, $this->paramModifiers[0]) === false) ? null :
sprintf
(
static::PARAMETERS_REGEX_FORMAT,
$this->paramModifiers[0],
$this->paramOptionalSymbol,
$this->paramModifiers[1]
);
if (preg_match_all('/' . $regex . '/is', $route, $parameters)) {
// Ensures that host names/domains will work with parameters
$url = '/' . ltrim($url, '/');
$urlRegex = '';
$parameters = [];
$urlParts = preg_split('/((\-?\/?)\{[^}]+\})/is', rtrim($route, '/'));
if ($regex === null || (bool)preg_match_all('/' . $regex . '/u', $route, $parameters) === false) {
$urlRegex = preg_quote($route, '/');
} else {
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];
$regex = isset($this->where[$name]) ? $this->where[$name] : $parameterRegex;
$regex = sprintf('\-?\/?(?P<%s>%s)', $name, $regex) . $parameters[2][$key];
/* If custom regex is defined, use that */
if (isset($this->where[$name]) === true) {
$regex = $this->where[$name];
} else {
$regex = $parameterRegex ?? $this->defaultParameterRegex ?? static::PARAMETERS_DEFAULT_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 = join('', $urlParts);
} else {
$urlRegex = preg_quote($route, '/');
}
if (preg_match('/^' . $urlRegex . '(\/?)$/is', $url, $matches) > 0) {
if (trim($urlRegex) === '' || (bool)preg_match(sprintf($this->urlRegex, $urlRegex), $url, $matches) === false) {
return null;
}
$values = [];
$values = [];
if (isset($parameters[1]) === true) {
/* Only take matched parameters with name */
foreach ($parameters[1] as $name) {
$values[$name] = isset($matches[$name]) ? $matches[$name] : null;
foreach ((array)$parameters[1] as $name) {
$values[$name] = (isset($matches[$name]) === true && $matches[$name] !== '') ? $matches[$name] : null;
}
return $values;
}
return null;
return $values;
}
/**
@@ -140,22 +169,22 @@ abstract class Route implements IRoute
*
* @return string
*/
public function getIdentifier()
public function getIdentifier(): string
{
if (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);
}
/**
* Set allowed request methods
*
* @param array $methods
* @return static $this
* @return static
*/
public function setRequestMethods(array $methods)
public function setRequestMethods(array $methods): IRoute
{
$this->requestMethods = $methods;
@@ -167,7 +196,7 @@ abstract class Route implements IRoute
*
* @return array
*/
public function getRequestMethods()
public function getRequestMethods(): array
{
return $this->requestMethods;
}
@@ -175,7 +204,7 @@ abstract class Route implements IRoute
/**
* @return IRoute|null
*/
public function getParent()
public function getParent(): ?IRoute
{
return $this->parent;
}
@@ -185,7 +214,7 @@ abstract class Route implements IRoute
*
* @return IGroupRoute|null
*/
public function getGroup()
public function getGroup(): ?IGroupRoute
{
return $this->group;
}
@@ -194,22 +223,24 @@ abstract class Route implements IRoute
* Set group
*
* @param IGroupRoute $group
* @return static $this
* @return static
*/
public function setGroup(IGroupRoute $group)
public function setGroup(IGroupRoute $group): IRoute
{
$this->group = $group;
return $this;
/* Add/merge parent settings with child */
return $this->setSettings($group->toArray(), true);
}
/**
* Set parent route
*
* @param IRoute $parent
* @return static $this
* @return static
*/
public function setParent(IRoute $parent)
public function setParent(IRoute $parent): IRoute
{
$this->parent = $parent;
@@ -219,10 +250,10 @@ abstract class Route implements IRoute
/**
* Set callback
*
* @param string $callback
* @param string|array|\Closure $callback
* @return static
*/
public function setCallback($callback)
public function setCallback($callback): IRoute
{
$this->callback = $callback;
@@ -230,16 +261,20 @@ abstract class Route implements IRoute
}
/**
* @return string
* @return string|callable
*/
public function getCallback()
{
return $this->callback;
}
public function getMethod()
public function getMethod(): ?string
{
if (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];
@@ -248,9 +283,13 @@ abstract class Route implements IRoute
return null;
}
public function getClass()
public function getClass(): ?string
{
if (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];
@@ -259,25 +298,25 @@ abstract class Route implements IRoute
return null;
}
public function setMethod($method)
public function setMethod(string $method): IRoute
{
$this->callback = sprintf('%s@%s', $this->getClass(), $method);
$this->callback = [$this->getClass(), $method];
return $this;
}
public function setClass($class)
public function setClass(string $class): IRoute
{
$this->callback = sprintf('%s@%s', $class, $this->getMethod());
$this->callback = [$class, $this->getMethod()];
return $this;
}
/**
* @param string $namespace
* @return static $this
* @return static
*/
public function setNamespace($namespace)
public function setNamespace(string $namespace): IRoute
{
$this->namespace = $namespace;
@@ -286,26 +325,26 @@ abstract class Route implements IRoute
/**
* @param string $namespace
* @return static $this
* @return static
*/
public function setDefaultNamespace($namespace)
public function setDefaultNamespace(string $namespace): IRoute
{
$this->defaultNamespace = $namespace;
return $this;
}
public function getDefaultNamespace()
public function getDefaultNamespace(): ?string
{
return $this->defaultNamespace;
}
/**
* @return string
* @return string|null
*/
public function getNamespace()
public function getNamespace(): ?string
{
return ($this->namespace === null) ? $this->defaultNamespace : $this->namespace;
return $this->namespace ?? $this->defaultNamespace;
}
/**
@@ -313,7 +352,7 @@ abstract class Route implements IRoute
*
* @return array
*/
public function toArray()
public function toArray(): array
{
$values = [];
@@ -321,49 +360,57 @@ 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;
}
if ($this->defaultParameterRegex !== null) {
$values['defaultParameterRegex'] = $this->defaultParameterRegex;
}
return $values;
}
/**
* Merge with information from another route.
*
* @param array $values
* @param array $settings
* @param bool $merge
* @return static $this
* @return static
*/
public function setSettings(array $values, $merge = false)
public function setSettings(array $settings, bool $merge = false): IRoute
{
if ($this->namespace === null && isset($values['namespace'])) {
$this->setNamespace($values['namespace']);
if ($this->namespace === null && isset($settings['namespace']) === true) {
$this->setNamespace($settings['namespace']);
}
if (isset($values['method'])) {
$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'])) {
$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'])) {
$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'])) {
$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($settings['defaultParameterRegex']) === true) {
$this->setDefaultParameterRegex($settings['defaultParameterRegex']);
}
return $this;
@@ -374,7 +421,7 @@ abstract class Route implements IRoute
*
* @return array
*/
public function getWhere()
public function getWhere(): array
{
return $this->where;
}
@@ -385,7 +432,7 @@ abstract class Route implements IRoute
* @param array $options
* @return static
*/
public function setWhere(array $options)
public function setWhere(array $options): IRoute
{
$this->where = $options;
@@ -396,9 +443,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)
{
@@ -410,12 +457,12 @@ abstract class Route implements IRoute
*
* @return array
*/
public function getParameters()
public function getParameters(): array
{
/* 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;
}
@@ -426,15 +473,15 @@ abstract class Route implements IRoute
* Get parameters
*
* @param array $parameters
* @return static $this
* @return static
*/
public function setParameters(array $parameters)
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) {
if (\count($parameters) !== 0 && \count($this->originalParameters) === 0) {
$this->originalParameters = $parameters;
}
@@ -444,12 +491,26 @@ abstract class Route implements IRoute
}
/**
* Set middleware class-name
* Add middleware class-name
*
* @param string $middleware
* @return static
* @deprecated This method is deprecated and will be removed in the near future.
*/
public function setMiddleware(string $middleware): self
{
$this->middlewares[] = $middleware;
return $this;
}
/**
* Add middleware class-name
*
* @param string $middleware
* @return static
*/
public function setMiddleware($middleware)
public function addMiddleware(string $middleware): IRoute
{
$this->middlewares[] = $middleware;
@@ -460,9 +521,9 @@ abstract class Route implements IRoute
* Set middlewares array
*
* @param array $middlewares
* @return $this
* @return static
*/
public function setMiddlewares(array $middlewares)
public function setMiddlewares(array $middlewares): IRoute
{
$this->middlewares = $middlewares;
@@ -470,11 +531,56 @@ abstract class Route implements IRoute
}
/**
* @return string|array
* @return array
*/
public function getMiddlewares()
public function getMiddlewares(): array
{
return $this->middlewares;
}
/**
* Set default regular expression used when matching parameters.
* This is used when no custom parameter regex is found.
*
* @param string $regex
* @return static
*/
public function setDefaultParameterRegex(string $regex): self
{
$this->defaultParameterRegex = $regex;
return $this;
}
/**
* Get default regular expression used when matching parameters.
*
* @return string
*/
public function getDefaultParameterRegex(): string
{
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;
}
}
@@ -1,4 +1,5 @@
<?php
namespace Pecee\SimpleRouter\Route;
use Pecee\Http\Request;
@@ -23,7 +24,7 @@ class RouteController extends LoadableRoute implements IControllerRoute
* @param string $name
* @return bool
*/
public function hasName($name)
public function hasName(string $name): bool
{
if ($this->name === null) {
return false;
@@ -34,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, false) === true && strtolower($this->name) === strtolower($newName)) {
if (\in_array($method, $this->names, true) === true && strtolower($this->name) === strtolower($newName)) {
return true;
}
}
@@ -48,12 +49,12 @@ class RouteController extends LoadableRoute implements IControllerRoute
* @param string|null $name
* @return string
*/
public function findUrl($method = null, $parameters = null, $name = null)
public function findUrl(?string $method = null, $parameters = null, ?string $name = null): string
{
if (strpos($name, '.') !== false) {
$found = array_search(substr($name, strrpos($name, '.') + 1), $this->names, false);
if ($found !== false) {
$method = $found;
$method = (string)$found;
}
}
@@ -63,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 = substr($method, strlen($requestType));
$method = (string)substr($method, \strlen($requestType));
break;
}
}
@@ -74,43 +75,44 @@ class RouteController extends LoadableRoute implements IControllerRoute
$method .= '/';
}
if ($this->getGroup() !== null && count($this->getGroup()->getDomains()) > 0) {
$url .= '//' . $this->getGroup()->getDomains()[0];
$group = $this->getGroup();
if ($group !== null && \count($group->getDomains()) !== 0) {
$url .= '//' . $group->getDomains()[0];
}
$url .= '/' . trim($this->getUrl(), '/') . '/' . strtolower($method) . join('/', $parameters);
$url .= '/' . trim($this->getUrl(), '/') . '/' . strtolower($method) . implode('/', $parameters);
return '/' . trim($url, '/') . '/';
}
public function matchRoute(Request $request)
public function matchRoute(string $url, Request $request): bool
{
$url = parse_url(urldecode($request->getUri()), PHP_URL_PATH);
$url = rtrim($url, '/') . '/';
/* Match global regular-expression for route */
if ($this->matchRegex($request, $url) === true) {
return true;
if ($this->getGroup() !== null && $this->getGroup()->matchRoute($url, $request) === false) {
return false;
}
if (stripos($url, $this->url) === 0 && strtolower($url) === strtolower($this->url)) {
/* Match global regular-expression for route */
$regexMatch = $this->matchRegex($request, $url);
$strippedUrl = trim(str_ireplace($this->url, '/', $url), '/');
if ($regexMatch === false || (stripos($url, $this->url) !== 0 && strtoupper($url) !== strtoupper($this->url))) {
return false;
}
$path = explode('/', $strippedUrl);
$strippedUrl = trim(str_ireplace($this->url, '/', $url), '/');
$path = explode('/', $strippedUrl);
if (count($path) > 0) {
if (\count($path) !== 0) {
$method = (!isset($path[0]) || trim($path[0]) === '') ? $this->defaultMethod : $path[0];
$this->method = $method;
$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);
// Set callback
$this->setCallback([$this->controller, $this->method]);
return true;
}
return true;
}
return false;
@@ -121,7 +123,7 @@ class RouteController extends LoadableRoute implements IControllerRoute
*
* @return string
*/
public function getController()
public function getController(): string
{
return $this->controller;
}
@@ -132,7 +134,7 @@ class RouteController extends LoadableRoute implements IControllerRoute
* @param string $controller
* @return static
*/
public function setController($controller)
public function setController(string $controller): IControllerRoute
{
$this->controller = $controller;
@@ -142,9 +144,9 @@ class RouteController extends LoadableRoute implements IControllerRoute
/**
* Return active method
*
* @return string
* @return string|null
*/
public function getMethod()
public function getMethod(): ?string
{
return $this->method;
}
@@ -155,7 +157,7 @@ class RouteController extends LoadableRoute implements IControllerRoute
* @param string $method
* @return static
*/
public function setMethod($method)
public function setMethod(string $method): IRoute
{
$this->method = $method;
@@ -165,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, $merge = false)
public function setSettings(array $settings, bool $merge = false): IRoute
{
if (isset($values['names'])) {
$this->names = $values['names'];
if (isset($settings['names']) === true) {
$this->names = $settings['names'];
}
parent::setSettings($values, $merge);
return $this;
return parent::setSettings($settings, $merge);
}
}
+65 -33
View File
@@ -1,7 +1,9 @@
<?php
namespace Pecee\SimpleRouter\Route;
use Pecee\Http\Request;
use Pecee\SimpleRouter\Handlers\IExceptionHandler;
class RouteGroup extends Route implements IGroupRoute
{
@@ -16,20 +18,23 @@ class RouteGroup extends Route implements IGroupRoute
* @param Request $request
* @return bool
*/
public function matchDomain(Request $request)
public function matchDomain(Request $request): bool
{
if (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;
}
}
@@ -40,26 +45,52 @@ class RouteGroup extends Route implements IGroupRoute
/**
* Method called to check if route matches
*
* @param string $url
* @param Request $request
* @return bool
*/
public function matchRoute(Request $request)
public function matchRoute(string $url, Request $request): bool
{
if ($this->getGroup() !== null && $this->getGroup()->matchRoute($url, $request) === false) {
return false;
}
// Parse parameter
$prefix = $this->prefix;
foreach ($this->getParameters() as $parameter => $value) {
$prefix = str_ireplace('{' . $parameter . '}', $value, $prefix);
}
/* Skip if prefix doesn't match */
if ($this->prefix !== null && stripos($request->getUri(), $this->prefix) === false) {
if ($this->prefix !== null && stripos($url, $prefix) === false) {
return false;
}
return $this->matchDomain($request);
}
/**
* Add exception handler
*
* @param IExceptionHandler|string $handler
* @return static
*/
public function addExceptionHandler($handler): IGroupRoute
{
$this->exceptionHandlers[] = $handler;
return $this;
}
/**
* Set exception-handlers for group
*
* @param array $handlers
* @return static $this
* @return static
*/
public function setExceptionHandlers(array $handlers)
public function setExceptionHandlers(array $handlers): IGroupRoute
{
$this->exceptionHandlers = $handlers;
@@ -71,7 +102,7 @@ class RouteGroup extends Route implements IGroupRoute
*
* @return array
*/
public function getExceptionHandlers()
public function getExceptionHandlers(): array
{
return $this->exceptionHandlers;
}
@@ -81,7 +112,7 @@ class RouteGroup extends Route implements IGroupRoute
*
* @return array
*/
public function getDomains()
public function getDomains(): array
{
return $this->domains;
}
@@ -90,9 +121,9 @@ class RouteGroup extends Route implements IGroupRoute
* Set allowed domains for group.
*
* @param array $domains
* @return $this
* @return static
*/
public function setDomains(array $domains)
public function setDomains(array $domains): IGroupRoute
{
$this->domains = $domains;
@@ -103,7 +134,7 @@ class RouteGroup extends Route implements IGroupRoute
* @param string $prefix
* @return static
*/
public function setPrefix($prefix)
public function setPrefix(string $prefix): IGroupRoute
{
$this->prefix = '/' . trim($prefix, '/');
@@ -113,9 +144,9 @@ class RouteGroup extends Route implements IGroupRoute
/**
* Set prefix that child-routes will inherit.
*
* @return string
* @return string|null
*/
public function getPrefix()
public function getPrefix(): ?string
{
return $this->prefix;
}
@@ -123,36 +154,37 @@ 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, $merge = false)
public function setSettings(array $settings, bool $merge = false): IRoute
{
if (isset($values['prefix'])) {
$this->setPrefix($values['prefix'] . $this->prefix);
if (isset($settings['prefix']) === true) {
$this->setPrefix($settings['prefix'] . $this->prefix);
}
if (isset($values['exceptionHandler'])) {
$this->setExceptionHandlers((array)$values['exceptionHandler']);
if ($merge === false && isset($settings['exceptionHandler']) === true) {
$this->setExceptionHandlers((array)$settings['exceptionHandler']);
}
if (isset($values['domain'])) {
$this->setDomains((array)$values['domain']);
if ($merge === false && isset($settings['domain']) === true) {
$this->setDomains((array)$settings['domain']);
}
if (isset($values['as'])) {
if (isset($settings['as']) === true) {
$name = $settings['as'];
if ($this->name !== null && $merge !== false) {
$this->name = $values['as'] . '.' . $this->name;
} else {
$this->name = $values['as'];
$name .= '.' . $this->name;
}
$this->name = $name;
}
parent::setSettings($values, $merge);
return $this;
return parent::setSettings($settings, $merge);
}
/**
@@ -160,7 +192,7 @@ class RouteGroup extends Route implements IGroupRoute
*
* @return array
*/
public function toArray()
public function toArray(): array
{
$values = [];
@@ -172,7 +204,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;
}
@@ -0,0 +1,47 @@
<?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(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);
}
return $this->matchDomain($request);
}
}
+50 -39
View File
@@ -1,4 +1,5 @@
<?php
namespace Pecee\SimpleRouter\Route;
use Pecee\Http\Request;
@@ -41,7 +42,7 @@ class RouteResource extends LoadableRoute implements IControllerRoute
* @param string $name
* @return bool
*/
public function hasName($name)
public function hasName(string $name): bool
{
if ($this->name === null) {
return false;
@@ -53,81 +54,93 @@ class RouteResource extends LoadableRoute implements IControllerRoute
/* Remove method/type */
if (strpos($name, '.') !== false) {
$name = substr($name, 0, strrpos($name, '.'));
$name = (string)substr($name, 0, strrpos($name, '.'));
}
return (strtolower($this->name) === strtolower($name));
}
public function findUrl($method = null, $parameters = null, $name = null)
/**
* @param string|null $method
* @param array|string|null $parameters
* @param string|null $name
* @return string
*/
public function findUrl(?string $method = null, $parameters = null, ?string $name = null): string
{
$method = array_search($name, $this->names, false);
if ($method !== false) {
return rtrim($this->url . $this->urls[$method], '/') . '/';
$url = array_search($name, $this->names, false);
if ($url !== false) {
return rtrim($this->url . $this->urls[$url], '/') . '/';
}
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(Request $request)
public function matchRoute(string $url, Request $request): bool
{
$url = parse_url(urldecode($request->getUri()), PHP_URL_PATH);
$url = rtrim($url, '/') . '/';
if ($this->getGroup() !== null && $this->getGroup()->matchRoute($url, $request) === false) {
return false;
}
/* Match global regular-expression for route */
$domainMatch = $this->matchRegex($request, $url);
if ($domainMatch !== null) {
return $domainMatch;
$regexMatch = $this->matchRegex($request, $url);
if ($regexMatch === false || (stripos($url, $this->url) !== 0 && strtoupper($url) !== strtoupper($this->url))) {
return false;
}
$route = rtrim($this->url, '/') . '/{id?}/{action?}';
$parameters = $this->parseParameters($route, $url);
if ($parameters === null) {
/* Parse parameters from current route */
$this->parameters = $this->parseParameters($route, $url);
/* If no custom regular expression or parameters was found on this route, we stop */
if ($regexMatch === null && $this->parameters === null) {
return false;
}
$this->parameters = (array)$parameters;
$action = strtolower(trim($this->parameters['action']));
$id = $this->parameters['id'];
$action = isset($this->parameters['action']) ? $this->parameters['action'] : null;
// Remove action parameter
unset($this->parameters['action']);
$method = $request->getMethod();
// Delete
if ($method === static::REQUEST_TYPE_DELETE && isset($this->parameters['id'])) {
if ($method === Request::REQUEST_TYPE_DELETE && $id !== null) {
return $this->call($this->methodNames['destroy']);
}
// Update
if (isset($this->parameters['id']) && in_array($method, [static::REQUEST_TYPE_PATCH, static::REQUEST_TYPE_PUT], false)) {
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 && isset($this->parameters['id']) && strtolower($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 && strtolower($action) === '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 && isset($this->parameters['id'])) {
if ($method === Request::REQUEST_TYPE_GET && $id !== null) {
return $this->call($this->methodNames['show']);
}
@@ -138,7 +151,7 @@ class RouteResource extends LoadableRoute implements IControllerRoute
/**
* @return string
*/
public function getController()
public function getController(): string
{
return $this->controller;
}
@@ -147,14 +160,14 @@ class RouteResource extends LoadableRoute implements IControllerRoute
* @param string $controller
* @return static
*/
public function setController($controller)
public function setController(string $controller): IControllerRoute
{
$this->controller = $controller;
return $this;
}
public function setName($name)
public function setName(string $name): ILoadableRoute
{
$this->name = $name;
@@ -177,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;
@@ -187,9 +200,9 @@ class RouteResource extends LoadableRoute implements IControllerRoute
/**
* Get method names
*
* @return array $this
* @return array
*/
public function getMethodNames()
public function getMethodNames(): array
{
return $this->methodNames;
}
@@ -197,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, $merge = false)
public function setSettings(array $settings, bool $merge = false): IRoute
{
if (isset($values['names'])) {
$this->names = $values['names'];
if (isset($settings['names']) === true) {
$this->names = $settings['names'];
}
if (isset($values['methods'])) {
$this->methodNames = $values['methods'];
if (isset($settings['methods']) === true) {
$this->methodNames = $settings['methods'];
}
parent::setSettings($values, $merge);
return $this;
return parent::setSettings($settings, $merge);
}
}
+20 -15
View File
@@ -1,4 +1,5 @@
<?php
namespace Pecee\SimpleRouter\Route;
use Pecee\Http\Request;
@@ -11,27 +12,31 @@ class RouteUrl extends LoadableRoute
$this->setCallback($callback);
}
public function matchRoute(Request $request)
public function matchRoute(string $url, Request $request): bool
{
$url = parse_url(urldecode($request->getUri()), PHP_URL_PATH);
$url = rtrim($url, '/') . '/';
/* Match global regular-expression for route */
$domainMatch = $this->matchRegex($request, $url);
if ($domainMatch !== null) {
return $domainMatch;
}
/* Make regular expression based on route */
$parameters = $this->parseParameters($this->url, $url);
if ($parameters === null) {
if ($this->getGroup() !== null && $this->getGroup()->matchRoute($url, $request) === false) {
return false;
}
$this->setParameters($parameters);
/* Match global regular-expression for route */
$regexMatch = $this->matchRegex($request, $url);
if ($regexMatch === false) {
return false;
}
/* Parse parameters from current route */
$parameters = $this->parseParameters($this->url, $url);
/* If no custom regular expression or parameters was found on this route, we stop */
if ($regexMatch === null && $parameters === null) {
return false;
}
/* Set the parameters */
$this->setParameters((array)$parameters);
return true;
}
}
File diff suppressed because it is too large Load Diff
+266 -79
View File
@@ -7,15 +7,24 @@
* 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.
*/
namespace Pecee\SimpleRouter;
use Pecee\Exceptions\InvalidArgumentException;
use Pecee\Http\Middleware\BaseCsrfVerifier;
use Pecee\Http\Request;
use Pecee\Http\Response;
use Pecee\Http\Url;
use Pecee\SimpleRouter\ClassLoader\IClassLoader;
use Pecee\SimpleRouter\Exceptions\HttpException;
use Pecee\SimpleRouter\Exceptions\NotFoundHttpException;
use Pecee\SimpleRouter\Handlers\CallbackExceptionHandler;
use Pecee\SimpleRouter\Handlers\IEventHandler;
use Pecee\SimpleRouter\Route\IGroupRoute;
use Pecee\SimpleRouter\Route\IPartialGroupRoute;
use Pecee\SimpleRouter\Route\IRoute;
use Pecee\SimpleRouter\Route\RouteController;
use Pecee\SimpleRouter\Route\RouteGroup;
use Pecee\SimpleRouter\Route\RoutePartialGroup;
use Pecee\SimpleRouter\Route\RouteResource;
use Pecee\SimpleRouter\Route\RouteUrl;
@@ -23,7 +32,7 @@ class SimpleRouter
{
/**
* Default namespace added to all routes
* @var string
* @var string|null
*/
protected static $defaultNamespace;
@@ -34,14 +43,81 @@ class SimpleRouter
protected static $response;
/**
* Start/route request
*
* @throws HttpException
* @throws NotFoundHttpException
* Router instance
* @var Router
*/
public static function start()
protected static $router;
/**
* Start routing
*
* @throws \Pecee\SimpleRouter\Exceptions\NotFoundHttpException
* @throws \Pecee\Http\Middleware\Exceptions\TokenMismatchException
* @throws HttpException
* @throws \Exception
*/
public static function start(): void
{
static::router()->routeRequest();
// Set default namespaces
foreach (static::router()->getRoutes() as $route) {
static::addDefaultNamespace($route);
}
echo static::router()->start();
}
/**
* Start the routing an return array with debugging-information
*
* @return array
*/
public static function startDebug(): array
{
$routerOutput = null;
try {
ob_start();
static::router()->setDebugEnabled(true)->start();
$routerOutput = ob_get_clean();
} catch (\Exception $e) {
}
// Try to parse library version
$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) {
foreach ($composerInfo['packages'] as $package) {
if (isset($package['name']) === true && strtolower($package['name']) === 'pecee/simple-router') {
$version = $package['version'];
break;
}
}
}
}
$request = static::request();
$router = static::router();
return [
'url' => $request->getUrl(),
'method' => $request->getMethod(),
'host' => $request->getHost(),
'loaded_routes' => $request->getLoadedRoutes(),
'all_routes' => $router->getRoutes(),
'boot_managers' => $router->getBootManagers(),
'csrf_verifier' => $router->getCsrfVerifier(),
'log' => $router->getDebugLog(),
'event_handlers' => $router->getEventHandlers(),
'router_output' => $routerOutput,
'library_version' => $version,
'php_version' => PHP_VERSION,
'server_params' => $request->getHeaders(),
];
}
/**
@@ -49,7 +125,7 @@ class SimpleRouter
*
* @param string $defaultNamespace
*/
public static function setDefaultNamespace($defaultNamespace)
public static function setDefaultNamespace(string $defaultNamespace): void
{
static::$defaultNamespace = $defaultNamespace;
}
@@ -59,98 +135,124 @@ class SimpleRouter
*
* @param BaseCsrfVerifier $baseCsrfVerifier
*/
public static function csrfVerifier(BaseCsrfVerifier $baseCsrfVerifier)
public static function csrfVerifier(BaseCsrfVerifier $baseCsrfVerifier): void
{
static::router()->setCsrfVerifier($baseCsrfVerifier);
}
/**
* Add new event handler to the router
*
* @param IEventHandler $eventHandler
*/
public static function addEventHandler(IEventHandler $eventHandler): void
{
static::router()->addEventHandler($eventHandler);
}
/**
* Boot managers allows you to alter the routes before the routing occurs.
* Perfect if you want to load pretty-urls from a file or database.
*
* @param IRouterBootManager $bootManager
*/
public static function addBootManager(IRouterBootManager $bootManager)
public static function addBootManager(IRouterBootManager $bootManager): void
{
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($url, $callback, array $settings = null)
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($url, $callback, array $settings = null)
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($url, $callback, array $settings = null)
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($url, $callback, array $settings = null)
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($url, $callback, array $settings = null)
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($url, $callback, array $settings = null)
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);
}
/**
@@ -158,19 +260,46 @@ class SimpleRouter
*
* @param array $settings
* @param \Closure $callback
* @throws \InvalidArgumentException
* @return RouteGroup
* @throws InvalidArgumentException
*/
public static function group(array $settings = [], \Closure $callback)
public static function group(array $settings, \Closure $callback): IGroupRoute
{
if (\is_callable($callback) === false) {
throw new InvalidArgumentException('Invalid callback provided. Only functions or methods supported');
}
$group = new RouteGroup();
$group->setCallback($callback);
$group->setSettings($settings);
if (is_callable($callback) === false) {
throw new \InvalidArgumentException('Invalid callback provided. Only functions or methods supported');
static::router()->addRoute($group);
return $group;
}
/**
* Special group that has the same benefits as group but supports
* parameters and which are only rendered when the url matches.
*
* @param string $url
* @param \Closure $callback
* @param array $settings
* @return RoutePartialGroup
* @throws InvalidArgumentException
*/
public static function partialGroup(string $url, \Closure $callback, array $settings = []): IPartialGroupRoute
{
if (\is_callable($callback) === false) {
throw new InvalidArgumentException('Invalid callback provided. Only functions or methods supported');
}
$settings['prefix'] = $url;
$group = new RoutePartialGroup();
$group->setSettings($settings);
$group->setCallback($callback);
static::router()->addRoute($group);
return $group;
@@ -180,14 +309,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($url, $callback, array $settings = null)
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);
}
/**
@@ -195,14 +324,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($url, $callback, array $settings = null)
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);
}
/**
@@ -210,45 +342,39 @@ class SimpleRouter
*
* @param array $requestMethods
* @param string $url
* @param string|\Closure $callback
* @param string|array|\Closure $callback
* @param array|null $settings
* @return RouteUrl
* @return RouteUrl|IRoute
*/
public static function match(array $requestMethods, $url, $callback, array $settings = null)
public static function match(array $requestMethods, string $url, $callback, array $settings = null)
{
$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
* @return RouteUrl|IRoute
*/
public static function all($url, $callback, array $settings = null)
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);
}
/**
@@ -257,20 +383,17 @@ class SimpleRouter
* @param string $url
* @param string $controller
* @param array|null $settings
* @return RouteController
* @return RouteController|IRoute
*/
public static function controller($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);
}
/**
@@ -279,9 +402,9 @@ class SimpleRouter
* @param string $url
* @param string $controller
* @param array|null $settings
* @return RouteResource
* @return RouteResource|IRoute
*/
public static function resource($url, $controller, array $settings = null)
public static function resource(string $url, string $controller, array $settings = null)
{
$route = new RouteResource($url, $controller);
@@ -289,9 +412,29 @@ class SimpleRouter
$route->setSettings($settings);
}
static::router()->addRoute($route);
return static::router()->addRoute($route);
}
return $route;
/**
* Add exception callback handler.
*
* @param \Closure $callback
* @return CallbackExceptionHandler $callbackHandler
*/
public static function error(\Closure $callback): CallbackExceptionHandler
{
$routes = static::router()->getRoutes();
$callbackHandler = new CallbackExceptionHandler($callback);
$group = new RouteGroup();
$group->addExceptionHandler($callbackHandler);
array_unshift($routes, $group);
static::router()->setRoutes($routes);
return $callbackHandler;
}
/**
@@ -309,20 +452,23 @@ class SimpleRouter
* @param string|null $name
* @param string|array|null $parameters
* @param array|null $getParams
* @throws \Exception
* @return string
* @return Url
*/
public static function getUrl($name = null, $parameters = null, $getParams = null)
public static function getUrl(?string $name = null, $parameters = null, ?array $getParams = null): Url
{
return static::router()->getUrl($name, $parameters, $getParams);
try {
return static::router()->getUrl($name, $parameters, $getParams);
} catch (\Exception $e) {
return new Url('/');
}
}
/**
* Get the request
*
* @return \Pecee\Http\Request
* @return Request
*/
public static function request()
public static function request(): Request
{
return static::router()->getRequest();
}
@@ -332,7 +478,7 @@ class SimpleRouter
*
* @return Response
*/
public static function response()
public static function response(): Response
{
if (static::$response === null) {
static::$response = new Response(static::request());
@@ -346,9 +492,13 @@ class SimpleRouter
*
* @return Router
*/
public static function router()
public static function router(): Router
{
return Router::getInstance();
if (static::$router === null) {
static::$router = new Router();
}
return static::$router;
}
/**
@@ -357,19 +507,56 @@ class SimpleRouter
* @param IRoute $route
* @return IRoute
*/
protected static function addDefaultNamespace(IRoute $route)
public static function addDefaultNamespace(IRoute $route): IRoute
{
if (static::$defaultNamespace !== null) {
$namespace = static::$defaultNamespace;
if ($route->getNamespace() !== null) {
$namespace .= '\\' . $route->getNamespace();
$ns = static::$defaultNamespace;
$namespace = $route->getNamespace();
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;
}
/**
* 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
*/
public static function enableMultiRouteRendering(bool $bool): void
{
static::router()->setRenderMultipleRoutes($bool);
}
/**
* Set custom class-loader class used.
* @param IClassLoader $classLoader
*/
public static function setCustomClassLoader(IClassLoader $classLoader): void
{
static::router()->setClassLoader($classLoader);
}
/**
* Get default namespace
* @return string|null
*/
public static function getDefaultNamespace(): ?string
{
return static::$defaultNamespace;
}
}
-25
View File
@@ -1,25 +0,0 @@
<?php
class DummyController
{
public function start()
{
echo static::class . '@' . 'start() OK';
}
public function param($params = null)
{
$params = func_get_args();
echo 'Params: ' . join(', ', $params);
}
public function notFound()
{
echo 'not found';
}
public function silent() {
}
}
-14
View File
@@ -1,14 +0,0 @@
<?php
require_once 'Exceptions/MiddlewareLoadedException.php';
use Pecee\Http\Middleware\IMiddleware;
use Pecee\Http\Request;
class DummyMiddleware implements IMiddleware
{
public function handle(Request $request, \Pecee\SimpleRouter\Route\ILoadableRoute &$route)
{
throw new MiddlewareLoadedException('Middleware loaded!');
}
}
-10
View File
@@ -1,10 +0,0 @@
<?php
class ExceptionHandler implements \Pecee\Handlers\IExceptionHandler
{
public function handleError(\Pecee\Http\Request $request, \Pecee\SimpleRouter\Route\ILoadableRoute &$route = null, \Exception $error)
{
echo $error->getMessage();
}
}
@@ -1,13 +0,0 @@
<?php
class TestExceptionHandlerFirst implements \Pecee\Handlers\IExceptionHandler
{
public function handleError(\Pecee\Http\Request $request, \Pecee\SimpleRouter\Route\ILoadableRoute &$route = null, \Exception $error)
{
echo 'ExceptionHandler 1 loaded' . chr(10);
$request->setUri('/');
return $request;
}
}
@@ -1,13 +0,0 @@
<?php
class TestExceptionHandlerSecond implements \Pecee\Handlers\IExceptionHandler
{
public function handleError(\Pecee\Http\Request $request, \Pecee\SimpleRouter\Route\ILoadableRoute &$route = null, \Exception $error)
{
echo 'ExceptionHandler 2 loaded' . chr(10);
$request->setUri('/');
return $request;
}
}
@@ -1,12 +0,0 @@
<?php
class TestExceptionHandlerThird implements \Pecee\Handlers\IExceptionHandler
{
public function handleError(\Pecee\Http\Request $request, \Pecee\SimpleRouter\Route\ILoadableRoute &$route = null, \Exception $error)
{
echo 'ExceptionHandler 3 loaded' . chr(10);
throw new ExceptionHandlerException('All good!', 666);
}
}
-95
View File
@@ -1,95 +0,0 @@
<?php
require_once 'Dummy/DummyMiddleware.php';
require_once 'Dummy/DummyController.php';
use Pecee\SimpleRouter\SimpleRouter as SimpleRouter;
class GroupTest extends PHPUnit_Framework_TestCase
{
protected $result;
public function testGroupLoad()
{
$this->result = false;
SimpleRouter::group(['prefix' => '/group'], function () {
$this->result = true;
});
try {
SimpleRouter::start();
} catch (Exception $e) {
// ignore RouteNotFound exception
}
$this->assertTrue($this->result);
}
public function testNestedGroup()
{
SimpleRouter::router()->reset();
SimpleRouter::request()->setUri('/api/v1/test');
SimpleRouter::request()->setMethod('get');
SimpleRouter::group(['prefix' => '/api'], function () {
SimpleRouter::group(['prefix' => '/v1'], function () {
SimpleRouter::get('/test', 'DummyController@start');
});
});
SimpleRouter::start();
}
public function testManyRoutes()
{
SimpleRouter::router()->reset();
SimpleRouter::request()->setUri('/my/match');
SimpleRouter::request()->setMethod('get');
SimpleRouter::group(['prefix' => '/api'], function () {
SimpleRouter::group(['prefix' => '/v1'], function () {
SimpleRouter::get('/test', 'DummyController@start');
});
});
SimpleRouter::get('/my/match', 'DummyController@start');
SimpleRouter::group(['prefix' => '/service'], function () {
SimpleRouter::group(['prefix' => '/v1'], function () {
SimpleRouter::get('/no-match', 'DummyController@start');
});
});
SimpleRouter::start();
}
public function testUrls()
{
SimpleRouter::router()->reset();
SimpleRouter::request()->setUri('/my/fancy/url/1');
SimpleRouter::request()->setMethod('get');
// Test array name
SimpleRouter::get('/my/fancy/url/1', 'DummyController@start', ['as' => 'fancy1']);
// Test method name
SimpleRouter::get('/my/fancy/url/2', 'DummyController@start')->setName('fancy2');
SimpleRouter::start();
$this->assertEquals('/my/fancy/url/1/', SimpleRouter::getUrl('fancy1'));
$this->assertEquals('/my/fancy/url/2/', SimpleRouter::getUrl('fancy2'));
}
}
-41
View File
@@ -1,41 +0,0 @@
<?php
require_once 'Dummy/DummyMiddleware.php';
require_once 'Dummy/DummyController.php';
require_once 'Dummy/Handler/ExceptionHandler.php';
use Pecee\SimpleRouter\SimpleRouter as SimpleRouter;
class MiddlewareTest extends PHPUnit_Framework_TestCase
{
public function testMiddlewareFound()
{
$this->setExpectedException('MiddlewareLoadedException');
SimpleRouter::router()->reset();
SimpleRouter::request()->setMethod('get');
SimpleRouter::request()->setUri('/my/test/url');
SimpleRouter::group(['exceptionHandler' => 'ExceptionHandler'], function () {
SimpleRouter::get('/my/test/url', 'DummyController@start', ['middleware' => 'DummyMiddleware']);
});
SimpleRouter::start();
}
public function testNestedMiddlewareLoad()
{
$this->setExpectedException('MiddlewareLoadedException');
SimpleRouter::router()->reset();
SimpleRouter::request()->setMethod('get');
SimpleRouter::request()->setUri('/my/test/url');
SimpleRouter::group(['exceptionHandler' => 'ExceptionHandler', 'middleware' => 'DummyMiddleware'], function () {
SimpleRouter::get('/my/test/url', 'DummyController@start');
});
SimpleRouter::start();
}
}
-172
View File
@@ -1,172 +0,0 @@
<?php
require_once 'Dummy/DummyMiddleware.php';
require_once 'Dummy/DummyController.php';
require_once 'Dummy/Exceptions/ExceptionHandlerException.php';
require_once 'Dummy/Handler/TestExceptionHandlerFirst.php';
require_once 'Dummy/Handler/TestExceptionHandlerSecond.php';
require_once 'Dummy/Handler/TestExceptionHandlerThird.php';
use Pecee\SimpleRouter\Exceptions\NotFoundHttpException as NotFoundHttpException;
use Pecee\SimpleRouter\SimpleRouter as SimpleRouter;
class RouterRouteTest extends PHPUnit_Framework_TestCase
{
protected $result = false;
public function testMultiParam()
{
SimpleRouter::router()->reset();
SimpleRouter::request()->setMethod('get');
SimpleRouter::request()->setUri('/test-param1-param2');
SimpleRouter::get('/test-{param1}-{param2}', function($param1, $param2) {
if($param1 === 'param1' && $param2 === 'param2') {
$this->result = true;
}
});
SimpleRouter::start();
$this->assertTrue($this->result);
}
/**
* Redirects to another route through 3 exception handlers.
*
* You will see "ExceptionHandler 1 loaded" 2 times. This happen because
* the exceptionhandler is asking the router to reload.
*
* That means that the exceptionhandler is loaded again, but this time
* the router ignores the same rewrite-route to avoid loop - loads
* the second which have same behavior and is also ignored before
* throwing the final Exception in ExceptionHandler 3.
*
* So this tests:
* 1. If ExceptionHandlers loads
* 2. If ExceptionHandlers load in the correct order
* 3. If ExceptionHandlers can rewrite the page on error
* 4. If the router can avoid redirect-loop due to developer has started loop.
* 5. And finally if we reaches the last exception-handler and that the correct
* exception-type is being thrown.
*/
public function testNotFound()
{
$this->setExpectedException('ExceptionHandlerException');
SimpleRouter::router()->reset();
SimpleRouter::request()->setMethod('get');
SimpleRouter::request()->setUri('/test-param1-param2');
SimpleRouter::group(['exceptionHandler' => ['TestExceptionHandlerFirst', 'TestExceptionHandlerSecond']], function () {
SimpleRouter::group(['exceptionHandler' => 'TestExceptionHandlerThird'], function () {
SimpleRouter::get('/non-existing-path', 'DummyController@start');
});
});
SimpleRouter::start();
}
public function testGet()
{
SimpleRouter::router()->reset();
SimpleRouter::request()->setUri('/my/test/url');
SimpleRouter::request()->setMethod('get');
SimpleRouter::get('/my/test/url', 'DummyController@start');
SimpleRouter::start();
}
public function testPost()
{
SimpleRouter::router()->reset();
SimpleRouter::request()->setUri('/my/test/url');
SimpleRouter::request()->setMethod('post');
SimpleRouter::post('/my/test/url', 'DummyController@start');
SimpleRouter::start();
}
public function testPut()
{
SimpleRouter::router()->reset();
SimpleRouter::request()->setUri('/my/test/url');
SimpleRouter::request()->setMethod('put');
SimpleRouter::put('/my/test/url', 'DummyController@start');
SimpleRouter::start();
}
public function testDelete()
{
SimpleRouter::router()->reset();
SimpleRouter::request()->setUri('/my/test/url');
SimpleRouter::request()->setMethod('delete');
SimpleRouter::delete('/my/test/url', 'DummyController@start');
SimpleRouter::start();
}
public function testMethodNotAllowed()
{
SimpleRouter::router()->reset();
SimpleRouter::request()->setUri('/my/test/url');
SimpleRouter::request()->setMethod('post');
SimpleRouter::get('/my/test/url', 'DummyController@start');
try {
SimpleRouter::start();
} catch (\Exception $e) {
$this->assertEquals(403, $e->getCode());
}
}
public function testSimpleParam()
{
SimpleRouter::router()->reset();
SimpleRouter::request()->setMethod('get');
SimpleRouter::request()->setUri('/test-param1');
SimpleRouter::get('/test-{param1}', 'DummyController@param');
SimpleRouter::start();
}
public function testPathParamRegex()
{
SimpleRouter::router()->reset();
SimpleRouter::request()->setMethod('get');
SimpleRouter::request()->setUri('/test/path/123123');
SimpleRouter::get('/test/path/{myParam}', 'DummyController@param', ['where' => ['myParam' => '([0-9]+)']]);
SimpleRouter::start();
}
public function testDomainRoute()
{
SimpleRouter::router()->reset();
SimpleRouter::request()->setMethod('get');
SimpleRouter::request()->setUri('/test');
SimpleRouter::request()->setHost('hello.world.com');
$this->result = false;
SimpleRouter::group(['domain' => '{subdomain}.world.com'], function () {
SimpleRouter::get('/test', function ($subdomain = null) {
$this->result = ($subdomain === 'hello');
});
});
SimpleRouter::start();
$this->assertTrue($this->result);
}
}
-114
View File
@@ -1,114 +0,0 @@
<?php
require_once 'Dummy/DummyMiddleware.php';
require_once 'Dummy/DummyController.php';
require_once 'Dummy/Handler/ExceptionHandler.php';
use Pecee\SimpleRouter\SimpleRouter as SimpleRouter;
class RouterUrlTest extends PHPUnit_Framework_TestCase
{
protected $result = false;
protected function getUrl($name = null, $parameters = null, array $getParams = [])
{
return SimpleRouter::getUrl($name, $parameters, $getParams);
}
public function testUrls()
{
SimpleRouter::router()->reset();
SimpleRouter::request()->setMethod('get');
SimpleRouter::request()->setUri('/');
// Match normal route on alias
SimpleRouter::get('/', 'DummyController@silent', ['as' => 'home']);
SimpleRouter::get('/about', 'DummyController@about');
SimpleRouter::group(['prefix' => '/admin', 'as' => 'admin'], function () {
// Match route with prefix on alias
SimpleRouter::get('/{id?}', 'DummyController@start', ['as' => 'home']);
// Match controller with prefix and alias
SimpleRouter::controller('/users', 'DummyController', ['as' => 'users']);
// Match controller with prefix and NO alias
SimpleRouter::controller('/pages', 'DummyController');
});
SimpleRouter::group(['prefix' => 'api', 'as' => 'api'], function () {
// Match resource controller
SimpleRouter::resource('phones', 'DummyController');
});
SimpleRouter::controller('gadgets', 'DummyController', ['names' => ['getIphoneInfo' => 'iphone']]);
// Match controller with no prefix and no alias
SimpleRouter::controller('/cats', 'CatsController');
// Pretend to load page
SimpleRouter::start();
$this->assertEquals('/gadgets/iphoneinfo/', $this->getUrl('gadgets.iphone'));
$this->assertEquals('/api/phones/create/', $this->getUrl('api.phones.create'));
// Should match /
$this->assertEquals('/', $this->getUrl('home'));
// Should match /about/
$this->assertEquals('/about/', $this->getUrl('DummyController@about'));
// Should match /admin/
$this->assertEquals('/admin/', $this->getUrl('DummyController@start'));
// Should match /admin/
$this->assertEquals('/admin/', $this->getUrl('admin.home'));
// Should match /admin/2/
$this->assertEquals('/admin/2/', $this->getUrl('admin.home', ['id' => 2]));
// Should match /admin/users/
$this->assertEquals('/admin/users/', $this->getUrl('admin.users'));
// Should match /admin/users/home/
$this->assertEquals('/admin/users/home/', $this->getUrl('admin.users@home'));
// Should match /cats/
$this->assertEquals('/cats/', $this->getUrl('CatsController'));
// Should match /cats/view/
$this->assertEquals('/cats/view/', $this->getUrl('CatsController', 'view'));
// Should match /cats/view/
//$this->assertEquals('/cats/view/', $this->getUrl('CatsController', ['view']));
// Should match /cats/view/666
$this->assertEquals('/cats/view/666/', $this->getUrl('CatsController@getView', ['666']));
// Should match /funny/man/
$this->assertEquals('/funny/man/', $this->getUrl('/funny/man'));
// Should match /?jackdaniels=true&cola=yeah
$this->assertEquals('/?jackdaniels=true&cola=yeah', $this->getUrl('home', null, ['jackdaniels' => 'true', 'cola' => 'yeah']));
}
public function testRegEx()
{
SimpleRouter::router()->reset();
SimpleRouter::request()->setMethod('get');
SimpleRouter::request()->setUri('/my/custom-path');
SimpleRouter::get('/my/{path}', 'DummyController@start')->where(['path' => '[a-zA-Z\-]+']);
SimpleRouter::start();
}
}
@@ -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,14 @@
<?php
class CustomClassLoader implements \Pecee\SimpleRouter\ClassLoader\IClassLoader
{
public function loadClass(string $class)
{
return new DummyController();
}
public function loadClosure(callable $closure, array $parameters)
{
return \call_user_func_array($closure, ['result' => true]);
}
}
@@ -0,0 +1,46 @@
<?php
class DummyController
{
public function index()
{
}
public function method1()
{
}
public function method2()
{
}
public function method3()
{
return 'method3';
}
public function param($params = null)
{
echo join(', ', func_get_args());
}
public function getTest()
{
echo 'getTest';
}
public function postTest()
{
echo 'postTest';
}
public function putTest()
{
echo 'putTest';
}
}
@@ -0,0 +1,13 @@
<?php
require_once 'Exception/MiddlewareLoadedException.php';
use Pecee\Http\Request;
class DummyMiddleware implements \Pecee\Http\Middleware\IMiddleware
{
public function handle(Request $request) : void
{
throw new MiddlewareLoadedException('Middleware loaded!');
}
}
@@ -1,4 +1,5 @@
<?php
class ExceptionHandlerException extends \Exception
{
}
@@ -0,0 +1,18 @@
<?php
class ResponseException extends \Exception
{
protected $response;
public function __construct($response)
{
$this->response = $response;
parent::__construct('', 0);
}
public function getResponse()
{
return $this->response;
}
}
@@ -0,0 +1,10 @@
<?php
class ExceptionHandler implements \Pecee\SimpleRouter\Handlers\IExceptionHandler
{
public function handleError(\Pecee\Http\Request $request, \Exception $error) : void
{
echo $error->getMessage();
}
}
@@ -0,0 +1,13 @@
<?php
class ExceptionHandlerFirst implements \Pecee\SimpleRouter\Handlers\IExceptionHandler
{
public function handleError(\Pecee\Http\Request $request, \Exception $error) : void
{
global $stack;
$stack[] = static::class;
$request->setUrl(new \Pecee\Http\Url('/'));
}
}
@@ -0,0 +1,13 @@
<?php
class ExceptionHandlerSecond implements \Pecee\SimpleRouter\Handlers\IExceptionHandler
{
public function handleError(\Pecee\Http\Request $request, \Exception $error) : void
{
global $stack;
$stack[] = static::class;
$request->setUrl(new \Pecee\Http\Url('/'));
}
}
@@ -0,0 +1,13 @@
<?php
class ExceptionHandlerThird implements \Pecee\SimpleRouter\Handlers\IExceptionHandler
{
public function handleError(\Pecee\Http\Request $request, \Exception $error) : void
{
global $stack;
$stack[] = static::class;
throw new ResponseException('ExceptionHandler loaded');
}
}
@@ -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;
}
}
}
@@ -0,0 +1,30 @@
<?php
class TestBootManager implements \Pecee\SimpleRouter\IRouterBootManager
{
protected $rewrite;
public function __construct(array $rewrite)
{
$this->rewrite = $rewrite;
}
/**
* 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
{
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($rewrite);
}
}
}
}
@@ -0,0 +1,16 @@
<?php
use Pecee\Http\Middleware\IMiddleware;
use Pecee\Http\Request;
class RewriteMiddleware implements IMiddleware {
public function handle(Request $request) : void {
$request->setRewriteCallback(function() {
return 'ok';
});
}
}
@@ -0,0 +1,39 @@
<?php
class ResourceController implements \Pecee\Controllers\IResourceController
{
public function index() : ?string
{
return 'index';
}
public function show($id) : ?string
{
return 'show ' . $id;
}
public function store() : ?string
{
return 'store';
}
public function create() : ?string
{
return 'create';
}
public function edit($id) : ?string
{
return 'edit ' . $id;
}
public function update($id) : ?string
{
return 'update ' . $id;
}
public function destroy($id) : ?string
{
return 'destroy ' . $id;
}
}
@@ -0,0 +1,41 @@
<?php
class SilentTokenProvider implements \Pecee\Http\Security\ITokenProvider {
protected $token;
public function __construct()
{
$this->refresh();
}
/**
* Refresh existing token
*/
public function refresh(): void
{
$this->token = uniqid('', false);
}
/**
* Validate valid CSRF token
*
* @param string $token
* @return bool
*/
public function validate(string $token): bool
{
return ($token === $this->token);
}
/**
* Get token token
*
* @param string|null $defaultValue
* @return string|null
*/
public function getToken(?string $defaultValue = null): ?string
{
return $this->token ?? $defaultValue;
}
}
@@ -0,0 +1,106 @@
<?php
require_once 'Dummy/DummyMiddleware.php';
require_once 'Dummy/DummyController.php';
require_once 'Dummy/Handler/ExceptionHandler.php';
require_once 'Dummy/Security/SilentTokenProvider.php';
require_once 'Dummy/Managers/TestBootManager.php';
use Pecee\SimpleRouter\Event\EventArgument;
use Pecee\SimpleRouter\Handlers\EventHandler;
class EventHandlerTest extends \PHPUnit\Framework\TestCase
{
public function testAllEventTriggered()
{
$events = EventHandler::$events;
// Remove the all event
unset($events[\array_search(EventHandler::EVENT_ALL, $events, true)]);
$eventHandler = new EventHandler();
$eventHandler->register(EventHandler::EVENT_ALL, function (EventArgument $arg) use (&$events) {
$key = \array_search($arg->getEventName(), $events, true);
unset($events[$key]);
});
TestRouter::addEventHandler($eventHandler);
// Add rewrite
TestRouter::error(function (\Pecee\Http\Request $request, \Exception $error) {
// Trigger rewrite
$request->setRewriteUrl('/');
});
TestRouter::get('/', 'DummyController@method1')->name('home');
// Trigger findRoute
TestRouter::router()->findRoute('home');
// Trigger getUrl
TestRouter::router()->getUrl('home');
// Add csrf-verifier
$csrfVerifier = new \Pecee\Http\Middleware\BaseCsrfVerifier();
$csrfVerifier->setTokenProvider(new SilentTokenProvider());
TestRouter::csrfVerifier($csrfVerifier);
// Add boot-manager
TestRouter::addBootManager(new TestBootManager([
'/test' => '/',
]));
// Start router
TestRouter::debug('/non-existing');
$this->assertEquals($events, []);
}
public function testAllEvent()
{
$status = false;
$eventHandler = new EventHandler();
$eventHandler->register(EventHandler::EVENT_ALL, function (EventArgument $arg) use (&$status) {
$status = true;
});
TestRouter::addEventHandler($eventHandler);
TestRouter::get('/', 'DummyController@method1');
TestRouter::debug('/');
// All event should fire for each other event
$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);
}
}
+84
View File
@@ -0,0 +1,84 @@
<?php
require_once 'Dummy/DummyMiddleware.php';
require_once 'Dummy/DummyController.php';
class GroupTest extends \PHPUnit\Framework\TestCase
{
public function testGroupLoad()
{
$result = false;
TestRouter::group(['prefix' => '/group'], function () use(&$result) {
$result = true;
});
try {
TestRouter::debug('/', 'get');
} catch(\Exception $e) {
}
$this->assertTrue($result);
}
public function testNestedGroup()
{
TestRouter::group(['prefix' => '/api'], function () {
TestRouter::group(['prefix' => '/v1'], function () {
TestRouter::get('/test', 'DummyController@method1');
});
});
TestRouter::debug('/api/v1/test', 'get');
$this->assertTrue(true);
}
public function testMultipleRoutes()
{
TestRouter::group(['prefix' => '/api'], function () {
TestRouter::group(['prefix' => '/v1'], function () {
TestRouter::get('/test', 'DummyController@method1');
});
});
TestRouter::get('/my/match', 'DummyController@method1');
TestRouter::group(['prefix' => '/service'], function () {
TestRouter::group(['prefix' => '/v1'], function () {
TestRouter::get('/no-match', 'DummyController@method1');
});
});
TestRouter::debug('/my/match', 'get');
$this->assertTrue(true);
}
public function testUrls()
{
// Test array name
TestRouter::get('/my/fancy/url/1', 'DummyController@method1', ['as' => 'fancy1']);
// Test method name
TestRouter::get('/my/fancy/url/2', 'DummyController@method1')->setName('fancy2');
TestRouter::debugNoReset('/my/fancy/url/1');
$this->assertEquals('/my/fancy/url/1/', TestRouter::getUrl('fancy1'));
$this->assertEquals('/my/fancy/url/2/', TestRouter::getUrl('fancy2'));
TestRouter::router()->reset();
}
}
@@ -0,0 +1,274 @@
<?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',
];
protected $brands = [
'Samsung',
'Apple',
'HP',
'Canon',
];
protected $sodas = [
0 => 'Pepsi',
1 => 'Coca Cola',
2 => 'Harboe',
3 => 'Mountain Dew',
];
protected $day = 'monday';
public function testPost()
{
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 testFile()
{
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 testFilesArray()
{
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()
{
global $_POST;
global $_GET;
$_POST = [
'names' => $this->names,
'is_sad' => true,
];
$_GET = [
'brands' => $this->brands,
'is_happy' => true,
];
$router = TestRouter::router();
$router->reset();
$router->getRequest()->setMethod('post');
$handler = TestRouter::request()->getInputHandler();
// GET
$brandsFound = $handler->all(['brands', 'nothing']);
$this->assertArrayHasKey('brands', $brandsFound);
$this->assertArrayHasKey('nothing', $brandsFound);
$this->assertEquals($this->brands, $brandsFound['brands']);
$this->assertNull($brandsFound['nothing']);
// POST
$namesFound = $handler->all(['names', 'nothing']);
$this->assertArrayHasKey('names', $namesFound);
$this->assertArrayHasKey('nothing', $namesFound);
$this->assertEquals($this->names, $namesFound['names']);
$this->assertNull($namesFound['nothing']);
// DEFAULT VALUE
$nonExisting = $handler->all([
'non-existing'
]);
$this->assertArrayHasKey('non-existing', $nonExisting);
$this->assertNull($nonExisting['non-existing']);
// Reset
$_GET = [];
$_POST = [];
}
protected function generateFile()
{
return [
'name' => uniqid('', false) . '.txt',
'type' => 'text/plain',
'tmp_name' => sys_get_temp_dir() . '/phpYfWUiw',
'error' => 0,
'size' => rand(3, 40),
];
}
protected function generateFileContent()
{
return md5(uniqid('', false));
}
}
@@ -0,0 +1,35 @@
<?php
require_once 'Dummy/DummyMiddleware.php';
require_once 'Dummy/DummyController.php';
require_once 'Dummy/Handler/ExceptionHandler.php';
class MiddlewareTest extends \PHPUnit\Framework\TestCase
{
public function testMiddlewareFound()
{
$this->expectException(MiddlewareLoadedException::class);
TestRouter::group(['exceptionHandler' => 'ExceptionHandler'], function () {
TestRouter::get('/my/test/url', 'DummyController@method1', ['middleware' => 'DummyMiddleware']);
});
TestRouter::debug('/my/test/url', 'get');
}
public function testNestedMiddlewareDontLoad()
{
TestRouter::group(['exceptionHandler' => 'ExceptionHandler', 'middleware' => 'DummyMiddleware'], function () {
TestRouter::get('/middleware', 'DummyController@method1');
});
TestRouter::get('/my/test/url', 'DummyController@method1');
TestRouter::debug('/my/test/url', 'get');
$this->assertTrue(true);
}
}
+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
}
@@ -0,0 +1,28 @@
<?php
require_once 'Dummy/DummyMiddleware.php';
require_once 'Dummy/DummyController.php';
require_once 'Dummy/Exception/ExceptionHandlerException.php';
class RouterCallbackExceptionHandlerTest extends \PHPUnit\Framework\TestCase
{
public function testCallbackExceptionHandler()
{
$this->expectException(ExceptionHandlerException::class);
// Match normal route on alias
TestRouter::get('/my-new-url', 'DummyController@method2');
TestRouter::get('/my-url', 'DummyController@method1');
TestRouter::error(function (\Pecee\Http\Request $request, \Exception $exception) {
throw new ExceptionHandlerException();
});
TestRouter::debugNoReset('/404-url', 'get');
TestRouter::router()->reset();
$this->assertTrue(true);
}
}
@@ -0,0 +1,41 @@
<?php
require_once 'Dummy/DummyController.php';
class RouterControllerTest extends \PHPUnit\Framework\TestCase
{
public function testGet()
{
// Match normal route on alias
TestRouter::controller('/url', 'DummyController');
$response = TestRouter::debugOutput('/url/test', 'get');
$this->assertEquals('getTest', $response);
}
public function testPost()
{
// Match normal route on alias
TestRouter::controller('/url', 'DummyController');
$response = TestRouter::debugOutput('/url/test', 'post');
$this->assertEquals('postTest', $response);
}
public function testPut()
{
// Match normal route on alias
TestRouter::controller('/url', 'DummyController');
$response = TestRouter::debugOutput('/url/test', 'put');
$this->assertEquals('putTest', $response);
}
}
@@ -0,0 +1,73 @@
<?php
require_once 'Dummy/DummyMiddleware.php';
require_once 'Dummy/DummyController.php';
require_once 'Dummy/Handler/ExceptionHandler.php';
class RouterPartialGroupTest extends \PHPUnit\Framework\TestCase
{
public function testParameters()
{
$result1 = null;
$result2 = null;
TestRouter::partialGroup('{param1}/{param2}', function ($param1 = null, $param2 = null) use (&$result1, &$result2) {
$result1 = $param1;
$result2 = $param2;
TestRouter::get('/', 'DummyController@method1');
});
TestRouter::debug('/param1/param2', 'get');
$this->assertEquals('param1', $result1);
$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);
}
}
@@ -0,0 +1,69 @@
<?php
require_once 'Dummy/ResourceController.php';
class RouterResourceTest extends \PHPUnit\Framework\TestCase
{
public function testResourceStore()
{
TestRouter::resource('/resource', 'ResourceController');
$response = TestRouter::debugOutput('/resource', 'post');
$this->assertEquals('store', $response);
}
public function testResourceCreate()
{
TestRouter::resource('/resource', 'ResourceController');
$response = TestRouter::debugOutput('/resource/create', 'get');
$this->assertEquals('create', $response);
}
public function testResourceIndex()
{
TestRouter::resource('/resource', 'ResourceController');
$response = TestRouter::debugOutput('/resource', 'get');
$this->assertEquals('index', $response);
}
public function testResourceDestroy()
{
TestRouter::resource('/resource', 'ResourceController');
$response = TestRouter::debugOutput('/resource/38', 'delete');
$this->assertEquals('destroy 38', $response);
}
public function testResourceEdit()
{
TestRouter::resource('/resource', 'ResourceController');
$response = TestRouter::debugOutput('/resource/38/edit', 'get');
$this->assertEquals('edit 38', $response);
}
public function testResourceUpdate()
{
TestRouter::resource('/resource', 'ResourceController');
$response = TestRouter::debugOutput('/resource/38', 'put');
$this->assertEquals('update 38', $response);
}
public function testResourceGet()
{
TestRouter::resource('/resource', 'ResourceController');
$response = TestRouter::debugOutput('/resource/38', 'get');
$this->assertEquals('show 38', $response);
}
}
@@ -0,0 +1,176 @@
<?php
require_once 'Dummy/DummyController.php';
require_once 'Dummy/Exception/ResponseException.php';
require_once 'Dummy/Handler/ExceptionHandlerFirst.php';
require_once 'Dummy/Handler/ExceptionHandlerSecond.php';
require_once 'Dummy/Handler/ExceptionHandlerThird.php';
require_once 'Dummy/Middleware/RewriteMiddleware.php';
class RouterRewriteTest extends \PHPUnit\Framework\TestCase
{
/**
* Redirects to another route through 3 exception handlers.
*
* You will see "ExceptionHandler 1 loaded" 2 times. This happen because
* the exceptionhandler is asking the router to reload.
*
* That means that the exceptionhandler is loaded again, but this time
* the router ignores the same rewrite-route to avoid loop - loads
* the second which have same behavior and is also ignored before
* throwing the final Exception in ExceptionHandler 3.
*
* So this tests:
* 1. If ExceptionHandlers loads
* 2. If ExceptionHandlers load in the correct order
* 3. If ExceptionHandlers can rewrite the page on error
* 4. If the router can avoid redirect-loop due to developer has started loop.
* 5. And finally if we reaches the last exception-handler and that the correct
* exception-type is being thrown.
*/
public function testExceptionHandlerRewrite()
{
global $stack;
$stack = [];
TestRouter::group(['exceptionHandler' => [ExceptionHandlerFirst::class, ExceptionHandlerSecond::class]], function () use ($stack) {
TestRouter::group(['exceptionHandler' => ExceptionHandlerThird::class], function () use ($stack) {
TestRouter::get('/my-path', 'DummyController@method1');
});
});
try {
TestRouter::debug('/my-non-existing-path', 'get');
} catch (\ResponseException $e) {
}
$expectedStack = [
ExceptionHandlerFirst::class,
ExceptionHandlerSecond::class,
ExceptionHandlerThird::class,
];
$this->assertEquals($expectedStack, $stack);
}
public function testRewriteExceptionMessage()
{
$this->expectException(\Pecee\SimpleRouter\Exceptions\NotFoundHttpException::class);
TestRouter::error(function (\Pecee\Http\Request $request, \Exception $error) {
if (strtolower($request->getUrl()->getPath()) === '/my/test/') {
$request->setRewriteUrl('/another-non-existing');
}
});
TestRouter::debug('/my/test', 'get');
}
public function testRewriteUrlFromRoute()
{
TestRouter::get('/old', function () {
TestRouter::request()->setRewriteUrl('/new');
});
TestRouter::get('/new', function () {
echo 'ok';
});
TestRouter::get('/new1', function () {
echo 'ok';
});
TestRouter::get('/new2', function () {
echo 'ok';
});
$output = TestRouter::debugOutput('/old');
$this->assertEquals('ok', $output);
}
public function testRewriteCallbackFromRoute()
{
TestRouter::get('/old', function () {
TestRouter::request()->setRewriteUrl('/new');
});
TestRouter::get('/new', function () {
return 'ok';
});
TestRouter::get('/new1', function () {
return 'fail';
});
TestRouter::get('/new/2', function () {
return 'fail';
});
$output = TestRouter::debugOutput('/old');
TestRouter::router()->reset();
$this->assertEquals('ok', $output);
}
public function testRewriteRouteFromRoute()
{
TestRouter::get('/match', function () {
TestRouter::request()->setRewriteRoute(new \Pecee\SimpleRouter\Route\RouteUrl('/match', function () {
return 'ok';
}));
});
TestRouter::get('/old1', function () {
return 'fail';
});
TestRouter::get('/old/2', function () {
return 'fail';
});
TestRouter::get('/new2', function () {
return 'fail';
});
$output = TestRouter::debugOutput('/match');
TestRouter::router()->reset();
$this->assertEquals('ok', $output);
}
public function testMiddlewareRewrite()
{
TestRouter::group(['middleware' => 'RewriteMiddleware'], function () {
TestRouter::get('/', function () {
return 'fail';
});
TestRouter::get('no/match', function () {
return 'fail';
});
});
$output = TestRouter::debugOutput('/');
$this->assertEquals('ok', $output);
}
}
@@ -0,0 +1,260 @@
<?php
require_once 'Dummy/DummyMiddleware.php';
require_once 'Dummy/DummyController.php';
require_once 'Dummy/Exception/ExceptionHandlerException.php';
class RouterRouteTest extends \PHPUnit\Framework\TestCase
{
/**
* Issue #421: Incorrectly optional character in route
*
* @throws Exception
*/
public function testOptionalCharacterRoute()
{
$result = false;
TestRouter::get('/api/v1/users/{userid}/projects/{id}/pages/{pageid?}', function () use (&$result) {
$result = true;
});
TestRouter::debug('/api/v1/users/1/projects/8399421535/pages/43/', 'get');
$this->assertTrue($result);
}
public function testMultiParam()
{
$result = false;
TestRouter::get('/test-{param1}-{param2}', function ($param1, $param2) use (&$result) {
if ($param1 === 'param1' && $param2 === 'param2') {
$result = true;
}
});
TestRouter::debug('/test-param1-param2', 'get');
$this->assertTrue($result);
}
public function testNotFound()
{
$this->expectException('\Pecee\SimpleRouter\Exceptions\NotFoundHttpException');
TestRouter::get('/non-existing-path', 'DummyController@method1');
TestRouter::debug('/test-param1-param2', 'post');
}
public function testGet()
{
TestRouter::get('/my/test/url', 'DummyController@method1');
TestRouter::debug('/my/test/url', 'get');
$this->assertTrue(true);
}
public function testPost()
{
TestRouter::post('/my/test/url', 'DummyController@method1');
TestRouter::debug('/my/test/url', 'post');
$this->assertTrue(true);
}
public function testPut()
{
TestRouter::put('/my/test/url', 'DummyController@method1');
TestRouter::debug('/my/test/url', 'put');
$this->assertTrue(true);
}
public function testDelete()
{
TestRouter::delete('/my/test/url', 'DummyController@method1');
TestRouter::debug('/my/test/url', 'delete');
$this->assertTrue(true);
}
public function testMethodNotAllowed()
{
TestRouter::get('/my/test/url', 'DummyController@method1');
try {
TestRouter::debug('/my/test/url', 'post');
} catch (\Exception $e) {
$this->assertEquals(403, $e->getCode());
}
}
public function testSimpleParam()
{
TestRouter::get('/test-{param1}', 'DummyController@param');
$response = TestRouter::debugOutput('/test-param1', 'get');
$this->assertEquals('param1', $response);
}
public function testPathParamRegex()
{
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) {
$result = ($subdomain === 'hello');
});
});
TestRouter::debug('/test', 'get');
$this->assertTrue($result);
}
public function testDomainNotAllowedRoute()
{
TestRouter::request()->setHost('other.world.com');
$result = false;
TestRouter::group(['domain' => '{subdomain}.world.com'], function () use (&$result) {
TestRouter::get('/test', function ($subdomain = null) use (&$result) {
$result = ($subdomain === 'hello');
});
});
TestRouter::debug('/test', 'get');
$this->assertFalse($result);
}
public function testRegEx()
{
TestRouter::get('/my/{path}', 'DummyController@method1')->where(['path' => '[a-zA-Z-]+']);
TestRouter::debug('/my/custom-path', 'get');
$this->assertTrue(true);
}
public function testParametersWithDashes()
{
$defaultVariable = null;
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;
});
TestRouter::debug('/my/');
$this->assertEquals('working', $defaultVariable);
}
public function testDefaultParameterRegex()
{
TestRouter::get('/my/{path}', 'DummyController@param', ['defaultParameterRegex' => '[\w-]+']);
$output = TestRouter::debugOutput('/my/custom-regex', 'get');
$this->assertEquals('custom-regex', $output);
}
public function testDefaultParameterRegexGroup()
{
TestRouter::group(['defaultParameterRegex' => '[\w-]+'], function () {
TestRouter::get('/my/{path}', 'DummyController@param');
});
$output = TestRouter::debugOutput('/my/custom-regex', 'get');
$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);
}
}
+274
View File
@@ -0,0 +1,274 @@
<?php
require_once 'Dummy/DummyMiddleware.php';
require_once 'Dummy/DummyController.php';
require_once 'Dummy/Handler/ExceptionHandler.php';
class RouterUrlTest extends \PHPUnit\Framework\TestCase
{
public function testIssue253()
{
TestRouter::get('/', 'DummyController@method1');
TestRouter::get('/page/{id?}', 'DummyController@method1');
TestRouter::get('/test-output', function () {
return 'return value';
});
TestRouter::debugNoReset('/page/22', 'get');
$this->assertEquals('/page/{id?}/', TestRouter::router()->getRequest()->getLoadedRoute()->getUrl());
TestRouter::debugNoReset('/', 'get');
$this->assertEquals('/', TestRouter::router()->getRequest()->getLoadedRoute()->getUrl());
$output = TestRouter::debugOutput('/test-output', 'get');
$this->assertEquals('return value', $output);
TestRouter::router()->reset();
}
public function testUnicodeCharacters()
{
// Test spanish characters
TestRouter::get('/cursos/listado/{listado?}/{category?}', 'DummyController@method1', ['defaultParameterRegex' => '[\w\p{L}\s\-]+']);
TestRouter::get('/test/{param}', 'DummyController@method1', ['defaultParameterRegex' => '[\w\p{L}\s\-\í]+']);
TestRouter::debugNoReset('/cursos/listado/especialidad/cirugía local', 'get');
$this->assertEquals('/cursos/listado/{listado?}/{category?}/', TestRouter::router()->getRequest()->getLoadedRoute()->getUrl());
TestRouter::debugNoReset('/test/Dermatología');
$parameters = TestRouter::request()->getLoadedRoute()->getParameters();
$this->assertEquals('Dermatología', $parameters['param']);
// Test danish characters
TestRouter::get('/kategori/økse', 'DummyController@method1', ['defaultParameterRegex' => '[\w\ø]+']);
TestRouter::debugNoReset('/kategori/økse', 'get');
$this->assertEquals('/kategori/økse/', TestRouter::router()->getRequest()->getLoadedRoute()->getUrl());
TestRouter::router()->reset();
}
public function testOptionalParameters()
{
TestRouter::get('/aviso/legal', 'DummyController@method1');
TestRouter::get('/aviso/{aviso}', 'DummyController@method1');
TestRouter::get('/pagina/{pagina}', 'DummyController@method1');
TestRouter::get('/{pagina?}', 'DummyController@method1');
TestRouter::debugNoReset('/aviso/optional', 'get');
$this->assertEquals('/aviso/{aviso}/', TestRouter::router()->getRequest()->getLoadedRoute()->getUrl());
TestRouter::debugNoReset('/pagina/optional', 'get');
$this->assertEquals('/pagina/{pagina}/', TestRouter::router()->getRequest()->getLoadedRoute()->getUrl());
TestRouter::debugNoReset('/optional', 'get');
$this->assertEquals('/{pagina?}/', TestRouter::router()->getRequest()->getLoadedRoute()->getUrl());
TestRouter::debugNoReset('/avisolegal', 'get');
$this->assertNotEquals('/aviso/{aviso}/', TestRouter::router()->getRequest()->getLoadedRoute()->getUrl());
TestRouter::debugNoReset('/avisolegal', 'get');
$this->assertEquals('/{pagina?}/', TestRouter::router()->getRequest()->getLoadedRoute()->getUrl());
TestRouter::router()->reset();
}
public function testSimilarUrls()
{
// Match normal route on alias
TestRouter::get('/url11', 'DummyController@method1');
TestRouter::get('/url22', 'DummyController@method2');
TestRouter::get('/url33', 'DummyController@method2')->name('match');
TestRouter::debugNoReset('/url33', 'get');
$this->assertEquals(TestRouter::getUrl('match'), TestRouter::getUrl());
TestRouter::router()->reset();
}
public function testUrls()
{
// Match normal route on alias
TestRouter::get('/', 'DummyController@method1', ['as' => 'home']);
TestRouter::get('/about', 'DummyController@about');
TestRouter::group(['prefix' => '/admin', 'as' => 'admin'], function () {
// Match route with prefix on alias
TestRouter::get('/{id?}', 'DummyController@method2', ['as' => 'home']);
// Match controller with prefix and alias
TestRouter::controller('/users', 'DummyController', ['as' => 'users']);
// Match controller with prefix and NO alias
TestRouter::controller('/pages', 'DummyController');
});
TestRouter::group(['prefix' => 'api', 'as' => 'api'], function () {
// Match resource controller
TestRouter::resource('phones', 'DummyController');
});
TestRouter::controller('gadgets', 'DummyController', ['names' => ['getIphoneInfo' => 'iphone']]);
// Match controller with no prefix and no alias
TestRouter::controller('/cats', 'CatsController');
// Pretend to load page
TestRouter::debugNoReset('/', 'get');
$this->assertEquals('/gadgets/iphoneinfo/', TestRouter::getUrl('gadgets.iphone'));
$this->assertEquals('/api/phones/create/', TestRouter::getUrl('api.phones.create'));
// Should match /
$this->assertEquals('/', TestRouter::getUrl('home'));
// Should match /about/
$this->assertEquals('/about/', TestRouter::getUrl('DummyController@about'));
// Should match /admin/
$this->assertEquals('/admin/', TestRouter::getUrl('DummyController@method2'));
// Should match /admin/
$this->assertEquals('/admin/', TestRouter::getUrl('admin.home'));
// Should match /admin/2/
$this->assertEquals('/admin/2/', TestRouter::getUrl('admin.home', ['id' => 2]));
// Should match /admin/users/
$this->assertEquals('/admin/users/', TestRouter::getUrl('admin.users'));
// Should match /admin/users/home/
$this->assertEquals('/admin/users/home/', TestRouter::getUrl('admin.users@home'));
// Should match /cats/
$this->assertEquals('/cats/', TestRouter::getUrl('CatsController'));
// Should match /cats/view/
$this->assertEquals('/cats/view/', TestRouter::getUrl('CatsController', 'view'));
// Should match /cats/view/
//$this->assertEquals('/cats/view/', TestRouter::getUrl('CatsController', ['view']));
// Should match /cats/view/666
$this->assertEquals('/cats/view/666/', TestRouter::getUrl('CatsController@getView', ['666']));
// Should match /funny/man/
$this->assertEquals('/funny/man/', TestRouter::getUrl('/funny/man'));
// Should match /?jackdaniels=true&cola=yeah
$this->assertEquals('/?jackdaniels=true&cola=yeah', TestRouter::getUrl('home', null, ['jackdaniels' => 'true', 'cola' => 'yeah']));
TestRouter::router()->reset();
}
public function testCustomRegex()
{
TestRouter::request()->setHost('google.com');
TestRouter::get('/admin/', function () {
return 'match';
})->setMatch('/^\/admin\/?(.*)/i');
$output = TestRouter::debugOutput('/admin/asd/bec/123', 'get');
$this->assertEquals('match', $output);
}
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();
}
}
+49
View File
@@ -0,0 +1,49 @@
<?php
class TestRouter extends \Pecee\SimpleRouter\SimpleRouter
{
public function __construct()
{
static::request()->setHost('testhost.com');
}
public static function debugNoReset($testUrl, $testMethod = 'get')
{
$request = static::request();
$request->setUrl((new \Pecee\Http\Url($testUrl))->setHost('local.unitTest'));
$request->setMethod($testMethod);
static::start();
}
public static function debug($testUrl, $testMethod = 'get', bool $reset = true)
{
try {
static::debugNoReset($testUrl, $testMethod);
} catch(\Exception $e) {
static::router()->reset();
throw $e;
}
if($reset === true) {
static::router()->reset();
}
}
public static function debugOutput($testUrl, $testMethod = 'get', bool $reset = true)
{
$response = null;
// Route request
ob_start();
static::debug($testUrl, $testMethod, $reset);
$response = ob_get_clean();
// Return response
return $response;
}
}
+4
View File
@@ -0,0 +1,4 @@
<?php
require_once dirname(__DIR__) . '/vendor/autoload.php';
require_once 'TestRouter.php';
+9
View File
@@ -0,0 +1,9 @@
<?php
require_once dirname(__DIR__) . '/vendor/autoload.php';
use \Pecee\SimpleRouter\SimpleRouter;
SimpleRouter::get('/user/{name}', 'UserController@show')->where(['name' => '[\w]+']);
$debugInfo = SimpleRouter::startDebug();
echo sprintf('<pre>%s</pre>', var_export($debugInfo, true));
exit;