Compare commits

...

286 Commits

Author SHA1 Message Date
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ø 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ø 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ø 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ø 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ø 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ø 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ø 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ø 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ø 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ø 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ø 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ø 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ø 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ø 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ø bf069c2d42 Fixed getExtension method in InputFile retuning empty value. 2017-02-01 07:51:35 +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ø 523d49359b Development
- Fixed where method calling itself instead of setWhere.
- Added unit-tests for regex.
2017-01-19 18:40:47 +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ø 7cc2652bcd getRoute optimisations. 2016-11-28 14:13:58 +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ø e26d55a810 getUrl will now use default-parameter value if specificially set to null when using url(). 2016-11-28 09:37:05 +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ø 8f3ce68a5e Optimised route-match behavior 2016-11-28 06:43:26 +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ø d25351f4f9 Fixed parameters not merged with default values 2016-11-28 05:53:02 +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ø e8f19fbeae Simplified PARAMETERS_REGEX_MATCH in Route class. 2016-11-28 05:24:12 +01:00
Simon Sessingø 5c89ae2aaf Moved regex/match functionality to LoadableRoute. 2016-11-28 04:45:46 +01:00
Simon Sessingø b694a7c0c9 More cleanup 2016-11-28 04:39:34 +01:00
Simon Sessingø 7847b71bbc Cleanup 2016-11-28 04:38:38 +01:00
Simon Sessingø d9b97ccf42 Bugfixes 2016-11-28 04:20:34 +01:00
Simon Sessingø 74351e0330 Bugfixes 2016-11-27 01:07:11 +01:00
Simon Sessingø f5b03e106c Bugfixes 2016-11-27 00:55:47 +01:00
Simon Sessingø 2c5221051e Development 2016-11-26 10:40:42 +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ø aad11ac581 Input optimisations 2016-11-26 05:21:09 +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ø c1835152b6 Removed PHP 7 specific functionality. 2016-11-26 04:53:05 +01:00
Simon Sessingø 6213f2fb75 Development
- Optimised Input-classes.
- `get` and `getObject` methods on `Input` now supports filtering on multiple method-types when using the `$method` parameter.
- Input classes now know how to parse that stupid nested $_FILES array.
- It's now possible to change method-names on ResourceControllers.
- Removed `getValue` and `setValue` from `InputFile` classes.
- Ensured that request-method are only parsed from $_POST or $_SERVER.
- Fixed minor parameter-issues with subdomain routing.
- Added PHPDocs.
- Added even more unit-tests.
- Many small optimisations tweaks.
2016-11-26 04:30:00 +01:00
Simon Sessingø a60fa9f919 Merge pull request #191 from skipperbent/v2
V2
2016-11-25 18:28:37 +01:00
Simon Sessingø ce6f34f4d4 Merge pull request #189 from skipperbent/v2
V2
2016-11-25 12:55:17 +01:00
Simon Sessingø 2868ffe023 Merge pull request #187 from skipperbent/v2
V2
2016-11-25 03:29:01 +02:00
Simon Sessingø 8ab52364da Merge pull request #185 from skipperbent/v2
V2
2016-11-25 03:06:03 +02:00
Simon Sessingø 110bc2afcf Merge pull request #183 from skipperbent/v2
V2
2016-11-25 02:58:55 +02:00
Simon Sessingø a92a4cdf0d Merge pull request #181 from skipperbent/v2
V2
2016-11-24 14:51:16 +02:00
Simon Sessingø 0a4c78cf64 Merge pull request #179 from skipperbent/v2
V2
2016-11-24 14:45:06 +02:00
Simon Sessingø 6706d86784 Merge pull request #177 from skipperbent/v2
V2
2016-11-24 13:19:00 +02:00
Simon Sessingø 20c1e0ef69 Merge pull request #175 from skipperbent/v2
V2
2016-11-24 11:06:10 +02:00
Simon Sessingø fe560a9ba5 Merge pull request #172 from skipperbent/v2
V2
2016-11-21 09:04:32 +02:00
Simon Sessingø c522801c28 Merge pull request #170 from skipperbent/v2
V2
2016-11-21 05:28:00 +02:00
Simon Sessingø 9d275d6d3b Merge pull request #168 from skipperbent/v2-development
Bugfixes
2016-11-21 05:08:12 +02:00
Simon Sessingø e6dd9f3f55 Merge pull request #166 from skipperbent/v2
V2
2016-11-21 04:09:35 +02:00
Simon Sessingø e803b1ca5c Merge pull request #164 from skipperbent/v2
V2
2016-11-19 21:13:12 +02:00
Simon Sessingø 45faf9830e Merge pull request #162 from skipperbent/v2
V2
2016-11-19 20:41:38 +02:00
Simon Sessingø c915c0386a Merge pull request #160 from skipperbent/v2
V2
2016-11-19 10:16:06 +02:00
Simon Sessingø 30479b15ca Merge pull request #158 from skipperbent/v2
V2
2016-11-19 09:47:29 +02:00
Simon Sessingø 4dde51e833 Merge pull request #156 from skipperbent/v2
V2
2016-11-19 07:02:34 +02:00
Simon Sessingø 961a4d0e94 Merge pull request #154 from skipperbent/v2
V2
2016-11-19 06:25:23 +02:00
Simon Sessingø 2216090a5f Merge pull request #152 from skipperbent/v2
V2
2016-11-17 19:01:25 +02:00
Simon Sessingø d467f60e55 Merge pull request #150 from skipperbent/v2
V2
2016-11-17 17:33:59 +02:00
Simon Sessingø c30ae6b098 Merge pull request #148 from skipperbent/v2
V2
2016-11-17 17:25:54 +02:00
Simon Sessingø c93308c8e1 Merge pull request #146 from skipperbent/v2
V2
2016-11-17 16:04:53 +02:00
Simon Sessingø c3241012af Merge pull request #144 from skipperbent/v2
V2
2016-11-17 15:21:32 +02:00
Simon Sessingø 9e2ba2674b Merge pull request #142 from skipperbent/v2
V2
2016-11-17 06:37:11 +02:00
Simon Sessingø 4d18f33a81 Merge pull request #140 from skipperbent/v2
V2
2016-11-17 05:03:39 +02:00
Simon Sessingø 9d98b2da4c Merge pull request #138 from skipperbent/v2
V2
2016-11-16 16:16:33 +02:00
Simon Sessingø 6930864e7e Merge pull request #136 from skipperbent/v2
V2
2016-11-16 15:32:45 +02:00
Simon Sessingø f3f129ae0b Merge pull request #134 from skipperbent/v2
V2
2016-11-15 09:47:07 +02:00
Simon Sessingø 85b9fac21e Merge pull request #132 from skipperbent/v2
V2
2016-11-15 08:13:22 +02:00
Simon Sessingø 0c93633d13 Merge pull request #130 from skipperbent/v2
V2
2016-11-15 01:12:51 +02:00
Simon Sessingø 973344e46e Merge pull request #128 from skipperbent/v2
V2
2016-11-08 18:23:22 +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
52 changed files with 3089 additions and 1402 deletions
+1 -2
View File
@@ -1,4 +1,3 @@
.idea
composer.lock
vendor/
demo-project/vendor
vendor/
+562 -237
View File
File diff suppressed because it is too large Load Diff
+32 -23
View File
@@ -1,26 +1,35 @@
{
"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"
],
"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/"
}
}
}
+80
View File
@@ -0,0 +1,80 @@
<?php
use Pecee\SimpleRouter\SimpleRouter as Router;
/**
* 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 string
*/
function url($name = null, $parameters = null, $getParams = null)
{
return Router::getUrl($name, $parameters, $getParams);
}
/**
* @return \Pecee\Http\Response
*/
function response()
{
return Router::response();
}
/**
* @return \Pecee\Http\Request
*/
function request()
{
return Router::request();
}
/**
* Get input class
* @param string|null $index Parameter index name
* @param string|null $defaultValue Default return value
* @param string|array|null $methods Default method
* @return \Pecee\Http\Input\Input|string
*/
function input($index = null, $defaultValue = null, $methods = null)
{
if ($index !== null) {
return request()->getInput()->get($index, $defaultValue, $methods);
}
return request()->getInput();
}
function redirect($url, $code = null)
{
if ($code !== null) {
response()->httpCode($code);
}
response()->redirect($url);
}
/**
* Get current csrf-token
* @return string|null
*/
function csrf_token()
{
$baseVerifier = Router::router()->getCsrfVerifier();
if ($baseVerifier !== null) {
return $baseVerifier->getTokenProvider()->getToken();
}
return null;
}
@@ -1,47 +1,47 @@
<?php
namespace Pecee\Controllers;
interface IRestController
interface IResourceController
{
/**
* @return void
*/
function index();
public function index();
/**
* @param mixed $id
* @return void
*/
function show($id);
public function show($id);
/**
* @return void
*/
function store();
public function store();
/**
* @return void
*/
function create();
public function create();
/**
* View
* @param mixed $id
* @return void
*/
function edit($id);
public function edit($id);
/**
* @param mixed $id
* @return void
*/
function update($id);
public function update($id);
/**
* @param mixed $id
* @return void
*/
function destroy($id);
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,38 @@
<?php
namespace Pecee\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\Handlers
*/
class CallbackExceptionHandler implements IExceptionHandler
{
protected $callback;
public function __construct(\Closure $callback)
{
$this->callback = $callback;
}
/**
* @param Request $request
* @param \Exception $error
* @return Request|null
*/
public function handleError(Request $request, \Exception $error)
{
/* Fire exceptions */
return call_user_func($this->callback,
$request,
$error
);
}
}
+1 -3
View File
@@ -2,16 +2,14 @@
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);
public function handleError(Request $request, \Exception $error);
}
-2
View File
@@ -14,8 +14,6 @@ interface IInputItem
public function getValue();
public function setValue($value);
public function __toString();
}
+147 -93
View File
@@ -1,4 +1,5 @@
<?php
namespace Pecee\Http\Input;
use Pecee\Http\Request;
@@ -32,150 +33,215 @@ class Input
$this->parseInputs();
}
protected function parseInputs()
public function parseInputs()
{
/* Parse get requests */
if (count($_GET) > 0) {
if (count($_GET) !== 0) {
$this->get = $this->handleGetPost($_GET);
}
/* Parse post requests */
$postVars = $_POST;
if (in_array($this->request->getMethod(), ['put', 'patch', 'delete']) === true) {
if (in_array($this->request->getMethod(), ['put', 'patch', 'delete'], false) === true) {
parse_str(file_get_contents('php://input'), $postVars);
}
if (count($postVars) > 0) {
if (count($postVars) !== 0) {
$this->post = $this->handleGetPost($postVars);
}
/* Parse get requests */
if (count($_FILES) > 0) {
$max = count($_FILES) - 1;
$keys = array_keys($_FILES);
for ($i = $max; $i >= 0; $i--) {
$key = $keys[$i];
$value = $_FILES[$key];
// Handle array input
if (is_array($value['name']) === false) {
$values['index'] = $key;
$this->file[$key] = InputFile::createFromArray(array_merge($value, $values));
continue;
}
$subMax = count($value['name']) - 1;
$keys = array_keys($value['name']);
$output = [];
for ($i = $subMax; $i >= 0; $i--) {
$output[$keys[$i]] = InputFile::createFromArray([
'index' => $key,
'error' => $value['error'][$keys[$i]],
'tmp_name' => $value['tmp_name'][$keys[$i]],
'type' => $value['type'][$keys[$i]],
'size' => $value['size'][$keys[$i]],
'name' => $value['name'][$keys[$i]],
]);
}
$this->file[$key] = $output;
}
if (count($_FILES) !== 0) {
$this->file = $this->parseFiles();
}
}
protected function handleGetPost($array)
public function parseFiles()
{
$tmp = [];
$list = [];
$max = count($array) - 1;
$keys = array_keys($array);
for ($i = $max; $i >= 0; $i--) {
$key = $keys[$i];
$value = $array[$key];
foreach ((array)$_FILES as $key => $value) {
// Handle array input
if (is_array($value) === false) {
$tmp[$key] = new InputItem($key, $value);
if (is_array($value['name']) === false) {
$values['index'] = $key;
$list[$key] = InputFile::createFromArray($values + $value);
continue;
}
$subMax = count($value) - 1;
$keys = array_keys($value);
$output = [];
$keys = [$key];
for ($i = $subMax; $i >= 0; $i--) {
$output[$keys[$i]] = new InputItem($key, $value[$keys[$i]]);
$files = $this->rearrangeFiles($value['name'], $keys, $value);
if (isset($list[$key]) === true) {
$list[$key][] = $files;
} else {
$list[$key] = $files;
}
$tmp[$key] = $output;
}
return $tmp;
return $list;
}
public function findPost($index, $default = null)
protected function rearrangeFiles(array $values, &$index, $original)
{
return isset($this->post[$index]) ? $this->post[$index] : $default;
$originalIndex = $index[0];
array_shift($index);
$output = [];
foreach ($values as $key => $value) {
if (is_array($original['name'][$key]) === false) {
$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;
}
$index[] = $key;
$files = $this->rearrangeFiles($value, $index, $original);
if (isset($output[$key]) === true) {
$output[$key][] = $files;
} else {
$output[$key] = $files;
}
}
return $output;
}
public function findFile($index, $default = null)
protected function handleGetPost(array $array)
{
return isset($this->file[$index]) ? $this->file[$index] : $default;
$list = [];
foreach ($array as $key => $value) {
// Handle array input
if (is_array($value) === false) {
$list[$key] = new InputItem($key, $value);
continue;
}
$output = $this->handleGetPost($value);
$list[$key] = $output;
}
return $list;
}
public function findGet($index, $default = null)
/**
* 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->get[$index]) ? $this->get[$index] : $default;
return isset($this->post[$index]) ? $this->post[$index] : $defaultValue;
}
public function getObject($index, $default = null, $method = null)
/**
* 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 ($method === null || strtolower($method) === 'get') {
if ($methods === null || in_array('get', $methods, false) === true) {
$element = $this->findGet($index);
}
if ($element === null && $method === null || strtolower($method) === 'post') {
if (($element === null && $methods === null) || ($methods !== null && in_array('post', $methods, false) === true)) {
$element = $this->findPost($index);
}
if ($element === null && $method === null || strtolower($method) === 'file') {
if (($element === null && $methods === null) || ($methods !== null && in_array('file', $methods, false) === true)) {
$element = $this->findFile($index);
}
return ($element === null) ? $default : $element;
return ($element !== null) ? $element : $defaultValue;
}
/**
* Get input element value matching index
*
* @param string $index
* @param string|null $default
* @param string|null $method
* @param string|null $defaultValue
* @param array|string|null $methods
* @return InputItem|string
*/
public function get($index, $default = null, $method = null)
public function get($index, $defaultValue = null, $methods = null)
{
$input = $this->getObject($index, $default, $method);
$input = $this->getObject($index, $defaultValue, $methods);
if ($input instanceof InputItem) {
return (trim($input->getValue()) === '') ? $default : $input->getValue();
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);
@@ -188,33 +254,21 @@ class Input
*/
public function all(array $filter = null)
{
$output = $_POST;
$output = $_GET;
if ($this->request->getMethod() === 'post') {
$contents = file_get_contents('php://input');
if (stripos(trim($contents), '{') === 0) {
$output = json_decode($contents, true);
if ($output === false) {
$output = [];
if (strpos(trim($contents), '{') === 0) {
$post = json_decode($contents, true);
if ($post !== false) {
$output = array_merge($output, $post);
}
}
}
$output = array_merge($_GET, $output);
if ($filter !== null) {
$output = array_filter($output, function ($key) use ($filter) {
if (in_array($key, $filter)) {
return true;
}
return false;
}, ARRAY_FILTER_USE_KEY);
}
return $output;
return ($filter !== null) ? array_intersect_key($output, array_flip($filter)) : $output;
}
}
+111 -39
View File
@@ -1,11 +1,12 @@
<?php
namespace Pecee\Http\Input;
class InputFile implements IInputItem
{
public $index;
public $value;
public $name;
public $filename;
public $size;
public $type;
public $error;
@@ -16,28 +17,39 @@ class InputFile implements IInputItem
$this->index = $index;
// 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
* @return static
*/
public static function createFromArray(array $values)
{
if (!isset($values['index'])) {
if (isset($values['index']) === false) {
throw new \InvalidArgumentException('Index key is required');
}
$input = new static($values['index']);
$input->setError(isset($values['error']) ? $values['error'] : null);
$input->setName(isset($values['name']) ? $values['name'] : null);
$input->setSize(isset($values['size']) ? $values['size'] : null);
$input->setType(isset($values['type']) ? $values['type'] : null);
$input->setTmpName(isset($values['tmp_name']) ? $values['tmp_name'] : null);
/* Easy way of ensuring that all indexes-are set and not filling the screen with isset() */
$values += [
'tmp_name' => null,
'type' => null,
'size' => null,
'name' => null,
'error' => null,
];
return (new static($values['index']))
->setSize($values['size'])
->setError($values['error'])
->setType($values['type'])
->setTmpName($values['tmp_name'])
->setFilename($values['name']);
return $input;
}
/**
@@ -48,6 +60,11 @@ class InputFile implements IInputItem
return $this->index;
}
/**
* Set input index
* @param string $index
* @return static $this
*/
public function setIndex($index)
{
$this->index = $index;
@@ -75,6 +92,10 @@ class InputFile implements IInputItem
return $this;
}
/**
* Get mime-type of file
* @return string
*/
public function getMime()
{
return $this->getType();
@@ -100,12 +121,19 @@ class InputFile implements IInputItem
return $this;
}
/**
* Returns extension without "."
*
* @return string
*/
public function getExtension()
{
return pathinfo($this->getName(), PATHINFO_EXTENSION);
return pathinfo($this->getFilename(), PATHINFO_EXTENSION);
}
/**
* Get human friendly name
*
* @return string
*/
public function getName()
@@ -113,6 +141,13 @@ class InputFile implements IInputItem
return $this->name;
}
/**
* Set human friendly name.
* Useful for adding validation etc.
*
* @param string $name
* @return static $this
*/
public function setName($name)
{
$this->name = $name;
@@ -120,29 +155,63 @@ class InputFile implements IInputItem
return $this;
}
/**
* Set filename
*
* @param string $name
* @return static $this
*/
public function setFilename($name)
{
$this->filename = $name;
return $this;
}
/**
* Get filename
*
* @return string mixed
*/
public function getFilename()
{
return $this->filename;
}
/**
* Move the uploaded temporary file to it's new home
*
* @param string $destination
* @return bool
*/
public function move($destination)
{
return move_uploaded_file($this->tmpName, $destination);
}
/**
* Get file contents
*
* @return string
*/
public function getContents()
{
return file_get_contents($this->tmpName);
}
public function setValue($value)
{
$this->value = $value;
return $this;
}
/**
* Return true if an upload error occurred.
*
* @return bool
*/
public function hasError()
{
return ($this->getError() !== 0);
}
/**
* Get upload-error code.
*
* @return string
*/
public function getError()
@@ -152,6 +221,7 @@ class InputFile implements IInputItem
/**
* Set error
*
* @param int $error
* @return static $this
*/
@@ -162,27 +232,6 @@ class InputFile implements IInputItem
return $this;
}
public function toArray()
{
return [
'tmp_name' => $this->tmpName,
'type' => $this->type,
'size' => $this->size,
'name' => $this->name,
'error' => $this->error,
];
}
public function __toString()
{
return $this->getValue();
}
public function getValue()
{
return $this->getTmpName();
}
/**
* @return string
*/
@@ -202,4 +251,27 @@ class InputFile implements IInputItem
return $this;
}
public function __toString()
{
return $this->getTmpName();
}
public function getValue()
{
return $this->getFilename();
}
public function toArray()
{
return [
'tmp_name' => $this->tmpName,
'type' => $this->type,
'size' => $this->size,
'name' => $this->name,
'error' => $this->error,
'filename' => $this->filename,
];
}
}
+2 -1
View File
@@ -13,7 +13,7 @@ class InputItem implements IInputItem
$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)));
}
/**
@@ -75,4 +75,5 @@ class InputItem implements IInputItem
{
return (string)$this->value;
}
}
+29 -31
View File
@@ -1,10 +1,11 @@
<?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
{
@@ -12,15 +13,11 @@ class BaseCsrfVerifier implements IMiddleware
const HEADER_KEY = 'X-CSRF-TOKEN';
protected $except;
protected $csrfToken;
protected $token;
protected $tokenProvider;
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();
}
/**
@@ -30,7 +27,7 @@ class BaseCsrfVerifier implements IMiddleware
*/
protected function skip(Request $request)
{
if ($this->except === null || is_array($this->except) === false) {
if ($this->except === null || count($this->except) === 0) {
return false;
}
@@ -42,9 +39,9 @@ class BaseCsrfVerifier implements IMiddleware
$url = rtrim($url, '/');
if ($url[strlen($url) - 1] === '*') {
$url = rtrim($url, '*');
$skip = (stripos($request->getUri(), $url) === 0);
$skip = (stripos($request->getUri()->getPath(), $url) === 0);
} else {
$skip = ($url === rtrim($request->getUri(), '/'));
$skip = ($url === $request->getUri()->getPath());
}
if ($skip === true) {
@@ -55,10 +52,16 @@ 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)
{
if (in_array($request->getMethod(), ['post', 'put', 'delete']) === true && $this->skip($request) === false) {
if ($this->skip($request) === false && in_array($request->getMethod(), ['post', 'put', 'delete'], false) === true) {
$token = $request->getInput()->get(static::POST_KEY, null, 'post');
@@ -67,34 +70,29 @@ class BaseCsrfVerifier implements IMiddleware
$token = $request->getHeader(static::HEADER_KEY);
}
if ($this->csrfToken->validate($token) === false) {
throw new TokenMismatchException('Invalid csrf-token.');
if ($this->tokenProvider->validate($token) === false) {
throw new TokenMismatchException('Invalid CSRF-token.');
}
}
// Refresh existing token
$this->tokenProvider->refresh();
}
public function generateToken()
public function getTokenProvider()
{
$token = $this->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)
{
if ($this->token != null) {
return true;
}
return $this->csrfToken->hasToken();
}
public function getToken()
{
return $this->token;
$this->tokenProvider = $provider;
}
}
+1 -3
View File
@@ -2,15 +2,13 @@
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);
}
+126 -37
View File
@@ -1,22 +1,40 @@
<?php
namespace Pecee\Http;
use Pecee\Http\Input\Input;
use Pecee\SimpleRouter\Route\ILoadableRoute;
use Pecee\SimpleRouter\Route\RouteUrl;
use Pecee\SimpleRouter\SimpleRouter;
class Request
{
protected $data = [];
private $data = [];
protected $headers;
protected $host;
protected $uri;
protected $method;
protected $input;
/**
* @var ILoadableRoute|null
*/
protected $rewriteRoute;
protected $rewriteUrl;
/**
* @var ILoadableRoute|null
*/
protected $loadedRoute;
public function __construct()
{
$this->parseHeaders();
$this->host = $this->getHeader('http-host');;
$this->uri = $this->getHeader('request-uri');
$this->setHost($this->getHeader('http-host'));
// Check if special IIS header exist, otherwise use default.
$this->setUri(new Uri($this->getHeader('unencoded-url', $this->getHeader('request-uri'))));
$this->input = new Input($this);
$this->method = strtolower($this->input->get('_method', $this->getHeader('request-method')));
}
@@ -25,13 +43,7 @@ class Request
{
$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;
}
@@ -40,15 +52,11 @@ class Request
public function isSecure()
{
if ($this->getHeader('http-x-forwarded-proto') === 'https' || $this->getHeader('https') !== null || $this->getHeader('server-port') === 443) {
return true;
}
return false;
return $this->getHeader('http-x-forwarded-proto') === 'https' || $this->getHeader('https') !== null || $this->getHeader('server-port') === 443;
}
/**
* @return string
* @return Uri
*/
public function getUri()
{
@@ -108,13 +116,24 @@ class Request
return $this->getHeader('http-cf-connecting-ip');
}
if ($this->getHeader('http-x-forwarded-for') !== null && strlen($this->getHeader('http-x-forwarded-for'))) {
if ($this->getHeader('http-x-forwarded-for') !== null) {
return $this->getHeader('http-x-forwarded_for');
}
return $this->getHeader('remote-addr');
}
/**
* Get remote address/ip
*
* @alias static::getIp
* @return string
*/
public function getRemoteAddr()
{
return $this->getIp();
}
/**
* Get referer
* @return string
@@ -137,30 +156,13 @@ class Request
* Get header value by name
*
* @param string $name
* @param object|null $defaultValue
* @param string|null $defaultValue
*
* @return string|null
*/
public function getHeader($name, $defaultValue = null)
{
if (isset($this->headers[strtolower($name)])) {
return $this->headers[strtolower($name)];
}
$max = count($_SERVER) - 1;
$keys = array_keys($_SERVER);
for ($i = $max; $i >= 0; $i--) {
$key = $keys[$i];
$name = $_SERVER[$key];
if ($key === $name) {
return $name;
}
}
return $defaultValue;
return isset($this->headers[strtolower($name)]) ? $this->headers[strtolower($name)] : $defaultValue;
}
/**
@@ -194,10 +196,14 @@ class Request
}
/**
* @param string $uri
* @param Uri|string $uri
*/
public function setUri($uri)
{
if (is_string($uri) === true) {
$uri = new Uri($uri);
}
$this->uri = $uri;
}
@@ -217,6 +223,89 @@ class Request
$this->method = $method;
}
/**
* Set rewrite route
*
* @param ILoadableRoute $route
* @return static
*/
public function setRewriteRoute(ILoadableRoute $route)
{
$this->rewriteRoute = SimpleRouter::addDefaultNamespace($route);
return $this;
}
/**
* Get rewrite route
*
* @return ILoadableRoute|null
*/
public function getRewriteRoute()
{
return $this->rewriteRoute;
}
/**
* Get rewrite url
*
* @return string
*/
public function getRewriteUrl()
{
return $this->rewriteUrl;
}
/**
* Set rewrite url
*
* @param string $rewriteUrl
* @return static
*/
public function setRewriteUrl($rewriteUrl)
{
$this->rewriteUrl = $rewriteUrl;
return $this;
}
/**
* Set rewrite callback
* @param string $callback
* @return static
*/
public function setRewriteCallback($callback)
{
return $this->setRewriteRoute(new RouteUrl($this->uri, $callback));
}
/**
* Get loaded route
* @return ILoadableRoute|null
*/
public function getLoadedRoute()
{
return $this->loadedRoute;
}
/**
* Set loaded route
*
* @param ILoadableRoute $route
* @return static
*/
public function setLoadedRoute(ILoadableRoute $route)
{
$this->loadedRoute = $route;
return $this;
}
public function __isset($name)
{
return array_key_exists($name, $this->data);
}
public function __set($name, $value = null)
{
$this->data[$name] = $value;
+19 -12
View File
@@ -1,4 +1,5 @@
<?php
namespace Pecee\Http;
class Response
@@ -41,7 +42,7 @@ class Response
public function refresh()
{
$this->redirect($this->request->getUri());
$this->redirect($this->request->getUri()->getPath());
}
/**
@@ -64,14 +65,14 @@ class Response
$this->headers([
'Cache-Control: public',
'Last-Modified: ' . gmdate("D, d M Y H:i:s", $lastModified) . ' GMT',
'Last-Modified: ' . gmdate('D, d M Y H:i:s', $lastModified) . ' GMT',
'Etag: ' . $eTag,
]);
$httpModified = $this->request->getHeader('http-if-modified-since');
$httpIfNoneMatch = $this->request->getHeader('http-if-none-match');
if ($httpModified !== null && strtotime($httpModified) == $lastModified || $httpIfNoneMatch !== null && $httpIfNoneMatch === $eTag) {
if (($httpIfNoneMatch !== null && $httpIfNoneMatch === $eTag) || ($httpModified !== null && strtotime($httpModified) === $lastModified)) {
$this->header('HTTP/1.1 304 Not Modified');
@@ -82,14 +83,21 @@ class Response
}
/**
* 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, $options = null, $dept = 512)
{
$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);
}
/**
@@ -111,9 +119,8 @@ class Response
*/
public function headers(array $headers)
{
$max = count($headers);
for ($i = 0; $i < $max; $i++) {
$this->header($headers[$i]);
foreach ($headers as $header) {
$this->header($header);
}
return $this;
@@ -0,0 +1,119 @@
<?php
namespace Pecee\Http\Security;
class CookieTokenProvider implements ITokenProvider
{
const CSRF_KEY = 'CSRF-TOKEN';
protected $token;
protected $cookieTimeoutMinutes = 120;
public function __construct()
{
$this->token = $this->getToken();
if ($this->token === null) {
$this->token = $this->generateToken();
}
}
/**
* Generate random identifier for CSRF token
*
* @throws \RuntimeException|\Exception
* @return string
*/
public function generateToken()
{
if (function_exists('random_bytes') === true) {
return bin2hex(random_bytes(32));
}
$isSourceStrong = false;
$random = openssl_random_pseudo_bytes(32, $isSourceStrong);
if ($isSourceStrong === false || $random === false) {
throw new \RuntimeException('IV generation failed');
}
return $random;
}
/**
* 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
* Overwrite this method to save the token to another storage like session etc.
*
* @param string $token
*/
public function setToken($token)
{
$this->token = $token;
setcookie(static::CSRF_KEY, $token, time() + 60 * $this->cookieTimeoutMinutes, '/');
}
/**
* Get csrf token
* @param string|null $defaultValue
* @return string|null
*/
public function getToken($defaultValue = null)
{
$this->token = ($this->hasToken() === true) ? $_COOKIE[static::CSRF_KEY] : null;
return ($this->token !== null) ? $this->token : $defaultValue;
}
/**
* Refresh existing token
*/
public function refresh()
{
if ($this->token !== null) {
$this->setToken($this->token);
}
}
/**
* Returns whether the csrf token has been defined
* @return bool
*/
public function hasToken()
{
return isset($_COOKIE[static::CSRF_KEY]);
}
/**
* Get timeout for cookie in minutes
* @return int
*/
public function getCookieTimeoutMinutes()
{
return $this->cookieTimeoutMinutes;
}
/**
* Set cookie timeout in minutes
* @param $minutes
*/
public function setCookieTimeoutMinutes($minutes)
{
$this->cookieTimeoutMinutes = $minutes;
}
}
@@ -0,0 +1,21 @@
<?php
namespace Pecee\Http\Security;
interface ITokenProvider
{
/**
* Refresh existing token
*/
public function refresh();
/**
* Validate valid CSRF token
*
* @param string $token
* @return bool
*/
public function validate($token);
}
+168
View File
@@ -0,0 +1,168 @@
<?php
namespace Pecee\Http;
class Uri
{
private $originalUrl;
private $data = [
'scheme' => null,
'host' => null,
'port' => null,
'user' => null,
'pass' => null,
'path' => null,
'query' => null,
'fragment' => null,
];
public function __construct($url)
{
$this->originalUrl = $url;
$this->data = $this->parseUrl($url) + $this->data;
if (isset($this->data['path']) === true && $this->data['path'] !== '/') {
$this->data['path'] = rtrim($this->data['path'], '/') . '/';
}
}
/**
* Check if url is using a secure protocol like https
* @return bool
*/
public function isSecure()
{
return (strtolower($this->getScheme()) === 'https');
}
/**
* Checks if url is relative
* @return bool
*/
public function isRelative()
{
return ($this->getHost() === null);
}
/**
* Get url scheme
* @return string|null
*/
public function getScheme()
{
return $this->data['scheme'];
}
/**
* Get url host
* @return string|null
*/
public function getHost()
{
return $this->data['host'];
}
/**
* Get url port
* @return int|null
*/
public function getPort()
{
return ($this->data['port'] !== null) ? (int)$this->data['port'] : null;
}
/**
* Parse username from url
* @return string|null
*/
public function getUserName()
{
return $this->data['user'];
}
/**
* Parse password from url
* @return string|null
*/
public function getPassword()
{
return $this->data['pass'];
}
/**
* Get path from url
* @return string
*/
public function getPath()
{
return $this->data['path'];
}
/**
* Get querystring from url
* @return string|null
*/
public function getQueryString()
{
return $this->data['query'];
}
/**
* Get fragment from url (everything after #)
* @return string|null
*/
public function getFragment()
{
return $this->data['fragment'];
}
/**
* @return string
*/
public function getOriginalUrl()
{
return $this->originalUrl;
}
/**
* UTF-8 aware parse_url() replacement.
* @param string $url
* @param int $component
* @throws \InvalidArgumentException
* @return array
*/
public function parseUrl($url, $component = -1)
{
$encodedUrl = preg_replace_callback(
'/[^:\/@?&=#]+/u',
function ($matches) {
return urlencode($matches[0]);
},
$url
);
$parts = parse_url($encodedUrl, $component);
if ($parts === false) {
throw new \InvalidArgumentException('Malformed URL: ' . $url);
}
return array_map('urldecode', $parts);
}
/**
* Returns data array with information about the url
* @return array
*/
public function getData()
{
return $this->data;
}
public function __toString()
{
return $this->getOriginalUrl();
}
}
@@ -3,4 +3,5 @@ namespace Pecee\SimpleRouter\Exceptions;
class NotFoundHttpException extends HttpException
{
}
@@ -1,6 +1,8 @@
<?php
namespace Pecee\SimpleRouter\Route;
use Pecee\Handlers\IExceptionHandler;
use Pecee\Http\Request;
interface IGroupRoute extends IRoute
@@ -13,6 +15,14 @@ interface IGroupRoute extends IRoute
*/
public function matchDomain(Request $request);
/**
* Add exception handler
*
* @param IExceptionHandler|string $handler
* @return static $this;
*/
public function addExceptionHandler($handler);
/**
* Set exception-handlers for group
*
@@ -20,9 +20,8 @@ interface ILoadableRoute extends IRoute
* Loads and renders middlewares-classes
*
* @param Request $request
* @param ILoadableRoute $route
*/
public function loadMiddleware(Request $request, ILoadableRoute &$route);
public function loadMiddleware(Request $request);
public function getUrl();
@@ -51,4 +50,19 @@ interface ILoadableRoute extends IRoute
*/
public function setName($name);
/**
* Get regular expression match used for matching route (if defined).
*
* @return string
*/
public function getMatch();
/**
* Add regular expression match for the entire route.
*
* @param string $regex
* @return static
*/
public function setMatch($regex);
}
@@ -0,0 +1,8 @@
<?php
namespace Pecee\SimpleRouter\Route;
interface IPartialGroupRoute
{
}
+6 -18
View File
@@ -1,4 +1,5 @@
<?php
namespace Pecee\SimpleRouter\Route;
use Pecee\Http\Request;
@@ -8,17 +9,19 @@ interface IRoute
/**
* Method called to check if a domain matches
*
* @param string $route
* @param Request $request
* @return bool
*/
public function matchRoute(Request $request);
public function matchRoute($route, Request $request);
/**
* Called when route is matched.
* Returns class to be rendered.
*
* @param Request $request
* @return object
* @throws \Pecee\SimpleRouter\Exceptions\NotFoundHttpException
* @return string
*/
public function renderRoute(Request $request);
@@ -112,21 +115,6 @@ interface IRoute
public function getDefaultNamespace();
/**
* Get regular expression match used for matching route (if defined).
*
* @return string
*/
public function getMatch();
/**
* Add regular expression match for the entire route.
*
* @param string $regex
* @return static
*/
public function setMatch($regex);
/**
* Get parameter names.
*
@@ -186,7 +174,7 @@ interface IRoute
* @param string $middleware
* @return static
*/
public function setMiddleware($middleware);
public function addMiddleware($middleware);
/**
* Set middlewares array
+98 -49
View File
@@ -1,4 +1,5 @@
<?php
namespace Pecee\SimpleRouter\Route;
use Pecee\Http\Middleware\IMiddleware;
@@ -7,36 +8,57 @@ use Pecee\SimpleRouter\Exceptions\HttpException;
abstract class LoadableRoute extends Route implements ILoadableRoute
{
const PARAMETERS_REGEX_MATCH = '%s([\w\-\_]*?)\%s{0,1}%s';
/**
* @var string
*/
protected $url;
/**
* @var string
*/
protected $name;
protected $regex;
/**
* Loads and renders middlewares-classes
*
* @param Request $request
* @param ILoadableRoute $route
* @throws HttpException
*/
public function loadMiddleware(Request $request, ILoadableRoute &$route)
public function loadMiddleware(Request $request)
{
if (count($this->getMiddlewares()) > 0) {
$max = count($this->getMiddlewares());
$max = count($this->getMiddlewares());
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 ($max === 0) {
return;
}
for ($i = 0; $i < $max; $i++) {
$middleware = $this->getMiddlewares()[$i];
if (is_object($middleware) === false) {
$middleware = $this->loadClass($middleware);
}
if (($middleware instanceof IMiddleware) === false) {
throw new HttpException($middleware . ' must be inherit the IMiddleware interface');
}
$middleware->handle($request);
}
}
public function matchRegex(Request $request, $url)
{
/* Match on custom defined regular expression */
if ($this->regex === null) {
return null;
}
return (preg_match($this->regex, $request->getHost() . $url) > 0);
}
/**
@@ -51,18 +73,11 @@ abstract class LoadableRoute extends Route implements ILoadableRoute
if (strpos($this->url, $this->paramModifiers[0]) !== false) {
$regex = sprintf(static::PARAMETERS_REGEX_MATCH, $this->paramModifiers[0], $this->paramOptionalSymbol, $this->paramModifiers[1]);
if (preg_match_all('/' . $regex . '/is', $this->url, $matches)) {
$max = count($matches[1]);
for ($i = 0; $i < $max; $i++) {
$this->parameters[$matches[1][$i]] = null;
}
$regex = sprintf(static::PARAMETERS_REGEX_FORMAT, $this->paramModifiers[0], $this->paramOptionalSymbol, $this->paramModifiers[1]);
if (preg_match_all('/' . $regex . '/u', $this->url, $matches) > 0) {
$this->parameters = array_fill_keys($matches[1], null);
}
}
return $this;
@@ -78,52 +93,60 @@ 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)
{
$url = '';
$url = $this->getUrl();
$parameters = (array)$parameters;
$group = $this->getGroup();
if ($this->getGroup() !== null && count($this->getGroup()->getDomains()) > 0) {
$url .= '//' . $this->getGroup()->getDomains()[0];
if ($group !== null && count($group->getDomains()) !== 0) {
$url = '//' . $group->getDomains()[0] . $url;
}
$url .= $this->getUrl();
$params = array_merge($this->getParameters(), $parameters);
/* Url that contains parameters that aren't recognized */
/* Contains parameters that aren't recognized and will be appended at the end of the url */
$unknownParams = [];
/* Create the param string - {} */
/* Create the param string - {parameter} */
$param1 = $this->paramModifiers[0] . '%s' . $this->paramModifiers[1];
/* Create the param string with the optional symbol - {?} */
/* Create the param string with the optional symbol - {parameter?} */
$param2 = $this->paramModifiers[0] . '%s' . $this->paramOptionalSymbol . $this->paramModifiers[1];
/* Let's parse the values of any {} parameter in the url */
/* 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 = $params[$param];
$value = (isset($parameters[$param])) ? $parameters[$param] : $value;
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])) {
$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;
}
}
$url .= join('/', $unknownParams);
$url = '/' . ltrim($url, '/');
$url .= implode('/', $unknownParams);
return rtrim($url, '/') . '/';
}
@@ -149,6 +172,29 @@ abstract class LoadableRoute extends Route implements ILoadableRoute
return (strtolower($this->name) === strtolower($name));
}
/**
* Add regular expression match for the entire route.
*
* @param string $regex
* @return static
*/
public function setMatch($regex)
{
$this->regex = $regex;
return $this;
}
/**
* Get regular expression match used for matching route (if defined).
*
* @return string
*/
public function getMatch()
{
return $this->regex;
}
/**
* Sets the router name, which makes it easier to obtain the url or router at a later point.
* Alias for LoadableRoute::setName().
@@ -184,15 +230,18 @@ abstract class LoadableRoute extends Route implements ILoadableRoute
*/
public function setSettings(array $values, $merge = false)
{
if (isset($values['as'])) {
if (isset($values['as']) === true) {
$name = $values['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'])) {
if (isset($values['prefix']) === true) {
$this->setUrl($values['prefix'] . $this->getUrl());
}
+217 -166
View File
@@ -1,12 +1,16 @@
<?php
namespace Pecee\SimpleRouter\Route;
use Pecee\Http\Middleware\IMiddleware;
use Pecee\Http\Request;
use Pecee\SimpleRouter\Exceptions\HttpException;
use Pecee\SimpleRouter\Exceptions\NotFoundHttpException;
abstract class Route implements IRoute
{
const PARAMETERS_REGEX_FORMAT = '%s([\w]+)(\%s?)%s';
const PARAMETERS_DEFAULT_REGEX = '[\w]+';
const REQUEST_TYPE_GET = 'get';
const REQUEST_TYPE_POST = 'post';
const REQUEST_TYPE_PUT = 'put';
@@ -23,8 +27,22 @@ abstract class Route implements IRoute
self::REQUEST_TYPE_DELETE,
];
/**
* If enabled parameters containing null-value
* will not be passed along to the callback.
*
* @var bool
*/
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;
@@ -32,138 +50,140 @@ abstract class Route implements IRoute
/* Default options */
protected $namespace;
protected $regex;
protected $requestMethods = [];
protected $where = [];
protected $parameters = [];
protected $originalParameters = [];
protected $middlewares = [];
public function renderRoute(Request $request)
{
if ($this->getCallback() !== null && is_callable($this->getCallback())) {
/* 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)) {
throw new NotFoundHttpException(sprintf('Method %s does not exist in class %s', $method, $className), 404);
}
$parameters = array_filter($this->getParameters(), function ($var) {
return ($var !== null);
});
call_user_func_array([$class, $method], $parameters);
return $class;
}
return null;
}
protected function parseParameters($route, $url, $parameterRegex = '[\w]+')
{
$parameterNames = [];
$regex = '';
$lastCharacter = '';
$isParameter = false;
$parameter = '';
$routeLength = strlen($route) - 1;
for ($i = $routeLength; $i >= 0; $i--) {
$character = strrev($route)[$i];
if ($character === '{') {
/* Remove "/" and "\" from regex */
if (substr($regex, strlen($regex) - 1) === '/') {
$regex = substr($regex, 0, strlen($regex) - 2);
}
$isParameter = true;
} elseif ($isParameter && $character === '}') {
$required = true;
/* Check for optional parameter and use custom parameter regex if it exists */
if (is_array($this->where) === true && isset($this->where[$parameter])) {
$parameterRegex = $this->where[$parameter];
}
if ($lastCharacter === '?') {
$parameter = substr($parameter, 0, strlen($parameter) - 1);
$regex .= '(?:\/?(?P<' . $parameter . '>' . $parameterRegex . ')[^\/]?)?';
$required = false;
} else {
$regex .= '\/?(?P<' . $parameter . '>' . $parameterRegex . ')[^\/]?';
}
$parameterNames[] = [
'name' => $parameter,
'required' => $required,
];
$parameter = '';
$isParameter = false;
} elseif ($isParameter) {
$parameter .= $character;
} elseif ($character === '/') {
$regex .= '\\' . $character;
} else {
$regex .= str_replace('.', '\\.', $character);
}
$lastCharacter = $character;
}
$parameterValues = [];
if (preg_match('/^' . $regex . '\/?$/is', $url, $parameterValues)) {
$parameters = [];
$max = count($parameterNames) - 1;
for ($i = $max; $i >= 0; $i--) {
$name = $parameterNames[$i];
$parameterValue = isset($parameterValues[$name['name']]) ? $parameterValues[$name['name']] : null;
if ($name['required'] && $parameterValue === null) {
throw new HttpException('Missing required parameter ' . $name['name'], 404);
}
if ($name['required'] === false && $parameterValue === null) {
continue;
}
$parameters[$name['name']] = $parameterValue;
}
return $parameters;
}
return null;
}
/**
* Load class by name
* @param string $name
* @return mixed
* @throws NotFoundHttpException
*/
protected function loadClass($name)
{
if (!class_exists($name)) {
throw new NotFoundHttpException(sprintf('Class %s does not exist', $name), 404);
if (class_exists($name) === false) {
throw new NotFoundHttpException(sprintf('Class "%s" does not exist', $name), 404);
}
return new $name();
}
/**
* Render route
*
* @param Request $request
* @return string|mixed
* @throws NotFoundHttpException
*/
public function renderRoute(Request $request)
{
$callback = $this->getCallback();
if ($callback === null) {
return null;
}
$parameters = $this->getParameters();
/* Filter parameters with null-value */
if ($this->filterEmptyParams === true) {
$parameters = array_filter($parameters, function ($var) {
return ($var !== null);
});
}
/* Render callback function */
if (is_callable($callback) === true) {
/* When the callback is a function */
return call_user_func_array($callback, $parameters);
}
/* When the callback is a class + method */
$controller = explode('@', $callback);
$namespace = $this->getNamespace();
$className = ($namespace !== null && $controller[0][0] !== '\\') ? $namespace . '\\' . $controller[0] : $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);
}
return call_user_func_array([$class, $method], $parameters);
}
protected function parseParameters($route, $url, $parameterRegex = null)
{
$regex = sprintf(static::PARAMETERS_REGEX_FORMAT, $this->paramModifiers[0], $this->paramOptionalSymbol, $this->paramModifiers[1]);
$parameters = [];
// Ensures that hostnames/domains will work with parameters
$url = '/' . ltrim($url, '/');
if (preg_match_all('/' . $regex . '/u', $route, $parameters) > 0) {
$urlParts = preg_split('/((\-?\/?)\{[^}]+\})/', $route);
foreach ($urlParts as $key => $t) {
$regex = '';
if ($key < count($parameters[1])) {
$name = $parameters[1][$key];
/* If custom regex is defined, use that */
if (isset($this->where[$name]) === true) {
$regex = $this->where[$name];
} else {
/* If method specific regex is defined use that, otherwise use the default parameter regex */
if ($parameterRegex !== null) {
$regex = $parameterRegex;
} else {
$regex = ($this->defaultParameterRegex === null) ? static::PARAMETERS_DEFAULT_REGEX : $this->defaultParameterRegex;
}
}
$regex = sprintf('(?:\/|\-)%1$s(?P<%2$s>%3$s)%1$s', $parameters[2][$key], $name, $regex);
}
$urlParts[$key] = preg_quote($t, '/') . $regex;
}
$urlRegex = implode('', $urlParts);
} else {
$urlRegex = preg_quote($route, '/');
}
if (preg_match(sprintf($this->urlRegex, $urlRegex), $url, $matches) > 0) {
$values = [];
if (isset($parameters[1]) === true) {
/* Only take matched parameters with name */
foreach ((array)$parameters[1] as $name) {
$values[$name] = (isset($matches[$name]) && $matches[$name] !== '') ? $matches[$name] : null;
}
}
return $values;
}
return null;
}
/**
* Returns callback name/identifier for the current route based on the callback.
* Useful if you need to get a unique identifier for the loaded route, for instance
@@ -173,7 +193,7 @@ abstract class Route implements IRoute
*/
public function getIdentifier()
{
if (strpos($this->callback, '@') !== false) {
if (is_string($this->callback) === true && strpos($this->callback, '@') !== false) {
return $this->callback;
}
@@ -270,7 +290,7 @@ abstract class Route implements IRoute
public function getMethod()
{
if (strpos($this->callback, '@') !== false) {
if (is_string($this->callback) === true && strpos($this->callback, '@') !== false) {
$tmp = explode('@', $this->callback);
return $tmp[1];
@@ -281,7 +301,7 @@ abstract class Route implements IRoute
public function getClass()
{
if (strpos($this->callback, '@') !== false) {
if (is_string($this->callback) === true && strpos($this->callback, '@') !== false) {
$tmp = explode('@', $this->callback);
return $tmp[0];
@@ -339,29 +359,6 @@ abstract class Route implements IRoute
return ($this->namespace === null) ? $this->defaultNamespace : $this->namespace;
}
/**
* Add regular expression match for the entire route.
*
* @param string $regex
* @return static
*/
public function setMatch($regex)
{
$this->regex = $regex;
return $this;
}
/**
* Get regular expression match used for matching route (if defined).
*
* @return string
*/
public function getMatch()
{
return $this->regex;
}
/**
* Export route settings to array so they can be merged with another route.
*
@@ -375,23 +372,20 @@ 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->parameters) > 0) {
/* Ensure the right order + values */
$parameters = ($values['parameters'] + $this->parameters);
$parameters = array_merge($parameters, $this->parameters);
$this->setParameters($parameters);
if (count($this->middlewares) !== 0) {
$values['middleware'] = $this->middlewares;
}
if (count($this->middlewares) > 0) {
$values['middleware'] = $this->middlewares;
if ($this->defaultParameterRegex !== null) {
$values['defaultParameterRegex'] = $this->defaultParameterRegex;
}
return $values;
@@ -406,27 +400,31 @@ abstract class Route implements IRoute
*/
public function setSettings(array $values, $merge = false)
{
if (isset($values['namespace']) && $this->namespace === null) {
if ($this->namespace === null && isset($values['namespace']) === true) {
$this->setNamespace($values['namespace']);
}
if (isset($values['method'])) {
if (isset($values['method']) === true) {
$this->setRequestMethods(array_merge($this->requestMethods, (array)$values['method']));
}
if (isset($values['where'])) {
if (isset($values['where']) === true) {
$this->setWhere(array_merge($this->where, (array)$values['where']));
}
if (isset($values['parameters'])) {
if (isset($values['parameters']) === true) {
$this->setParameters(array_merge($this->parameters, (array)$values['parameters']));
}
// Push middleware if multiple
if (isset($values['middleware'])) {
if (isset($values['middleware']) === true) {
$this->setMiddlewares(array_merge((array)$values['middleware'], $this->middlewares));
}
if (isset($values['defaultParameterRegex']) === true) {
$this->setDefaultParameterRegex($values['defaultParameterRegex']);
}
return $this;
}
@@ -463,7 +461,7 @@ abstract class Route implements IRoute
*/
public function where(array $options)
{
return $this->where($options);
return $this->setWhere($options);
}
/**
@@ -473,7 +471,14 @@ abstract class Route implements IRoute
*/
public function getParameters()
{
return $this->parameters;
/* Sort the parameters after the user-defined param order, if any */
$parameters = [];
if (count($this->originalParameters) !== 0) {
$parameters = $this->originalParameters;
}
return array_merge($parameters, $this->parameters);
}
/**
@@ -484,15 +489,24 @@ abstract class Route implements IRoute
*/
public function setParameters(array $parameters)
{
$this->parameters = $parameters;
/*
* If this is the first time setting parameters we store them so we
* later can organize the array, in case somebody tried to sort the array.
*/
if (count($parameters) !== 0 && count($this->originalParameters) === 0) {
$this->originalParameters = $parameters;
}
$this->parameters = array_merge($this->parameters, $parameters);
return $this;
}
/**
* Set middleware class-name
* Add middleware class-name
*
* @param string $middleware
* @deprecated This method is deprecated and will be removed in the near future.
* @param IMiddleware|string $middleware
* @return static
*/
public function setMiddleware($middleware)
@@ -502,6 +516,19 @@ abstract class Route implements IRoute
return $this;
}
/**
* Add middleware class-name
*
* @param IMiddleware|string $middleware
* @return static
*/
public function addMiddleware($middleware)
{
$this->middlewares[] = $middleware;
return $this;
}
/**
* Set middlewares array
*
@@ -523,4 +550,28 @@ abstract class Route implements IRoute
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 $this
*/
public function setDefaultParameterRegex($regex)
{
$this->defaultParameterRegex = $regex;
return $this;
}
/**
* Get default regular expression used when matching parameters.
*
* @return string
*/
public function getDefaultParameterRegex()
{
return $this->defaultParameterRegex;
}
}
@@ -1,8 +1,8 @@
<?php
namespace Pecee\SimpleRouter\Route;
use Pecee\Http\Request;
use Pecee\SimpleRouter\Exceptions\NotFoundHttpException;
class RouteController extends LoadableRoute implements IControllerRoute
{
@@ -31,11 +31,11 @@ class RouteController extends LoadableRoute implements IControllerRoute
}
/* Remove method/type */
if (stripos($name, '.') !== false) {
if (strpos($name, '.') !== false) {
$method = substr($name, strrpos($name, '.') + 1);
$newName = substr($name, 0, strrpos($name, '.'));
if (strtolower($this->name) === strtolower($newName) && in_array($method, $this->names)) {
if (in_array($method, $this->names, false) === true && strtolower($this->name) === strtolower($newName)) {
return true;
}
}
@@ -43,31 +43,31 @@ class RouteController extends LoadableRoute implements IControllerRoute
return parent::hasName($name);
}
/**
* @param string|null $method
* @param string|array|null $parameters
* @param string|null $name
* @return string
*/
public function findUrl($method = null, $parameters = null, $name = null)
{
if (stripos($name, '.') !== false) {
$found = array_search(substr($name, strrpos($name, '.') + 1), $this->names);
if (strpos($name, '.') !== false) {
$found = array_search(substr($name, strrpos($name, '.') + 1), $this->names, false);
if ($found !== false) {
$method = $found;
$method = (string)$found;
}
}
$url = '';
$parameters = (array)$parameters;
/* Remove requestType from method-name, if it exists */
if ($method !== null) {
$max = count(static::$requestTypes);
for ($i = 0; $i < $max; $i++) {
$requestType = static::$requestTypes[$i];
/* Remove requestType from method-name, if it exists */
foreach (static::$requestTypes as $requestType) {
if (stripos($method, $requestType) === 0) {
$method = substr($method, strlen($requestType));
$method = (string)substr($method, strlen($requestType));
break;
}
}
@@ -75,68 +75,43 @@ 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 renderRoute(Request $request)
public function matchRoute($url, Request $request)
{
if ($this->getCallback() !== null && is_callable($this->getCallback())) {
/* Match global regular-expression for route */
$regexMatch = $this->matchRegex($request, $url);
// 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 = $request->getMethod() . ucfirst($controller[1]);
if (!method_exists($class, $method)) {
throw new NotFoundHttpException(sprintf('Method %s does not exist in class %s', $method, $className), 404);
}
call_user_func_array([$class, $method], $this->getParameters());
return $class;
if ($regexMatch === false || (stripos($url, $this->url) !== 0 && strtolower($url) !== strtolower($this->url))) {
return false;
}
return null;
}
$strippedUrl = trim(str_ireplace($this->url, '/', $url), '/');
$path = explode('/', $strippedUrl);
public function matchRoute(Request $request)
{
$url = parse_url(urldecode($request->getUri()), PHP_URL_PATH);
$url = rtrim($url, '/') . '/';
if (count($path) !== 0) {
if (strtolower($url) == strtolower($this->url) || stripos($url, $this->url) === 0) {
$method = (isset($path[0]) === false || trim($path[0]) === '') ? $this->defaultMethod : $path[0];
$this->method = $request->getMethod() . ucfirst($method);
$strippedUrl = trim(str_ireplace($this->url, '/', $url), '/');
$this->parameters = array_slice($path, 1);
$path = explode('/', $strippedUrl);
// Set callback
$this->setCallback($this->controller . '@' . $this->method);
if (count($path) > 0) {
$method = (!isset($path[0]) || trim($path[0]) === '') ? $this->defaultMethod : $path[0];
$this->method = $method;
array_shift($path);
$this->parameters = $path;
// Set callback
$this->setCallback($this->controller . '@' . $this->method);
return true;
}
return true;
}
return null;
return false;
}
/**
@@ -194,7 +169,7 @@ class RouteController extends LoadableRoute implements IControllerRoute
*/
public function setSettings(array $values, $merge = false)
{
if (isset($values['names'])) {
if (isset($values['names']) === true) {
$this->names = $values['names'];
}
+48 -28
View File
@@ -1,6 +1,8 @@
<?php
namespace Pecee\SimpleRouter\Route;
use Pecee\Handlers\IExceptionHandler;
use Pecee\Http\Request;
class RouteGroup extends Route implements IGroupRoute
@@ -18,44 +20,55 @@ class RouteGroup extends Route implements IGroupRoute
*/
public function matchDomain(Request $request)
{
if (count($this->domains) > 0) {
$max = count($this->domains) - 1;
for ($i = $max; $i >= 0; $i--) {
$domain = $this->domains[$i];
$parameters = $this->parseParameters($domain, $request->getHost(), '.*');
if ($parameters !== null) {
$this->parameters = $parameters;
return true;
}
}
return false;
if ($this->domains === null || count($this->domains) === 0) {
return true;
}
return true;
foreach ($this->domains as $domain) {
$parameters = $this->parseParameters($domain, $request->getHost(), '.*');
if ($parameters !== null && count($parameters) !== 0) {
$this->parameters = $parameters;
return true;
}
}
return false;
}
/**
* Method called to check if route matches
*
* @param string $url
* @param Request $request
* @return bool
*/
public function matchRoute(Request $request)
public function matchRoute($url, Request $request)
{
// Skip if prefix doesn't match
if ($this->prefix !== null && stripos($request->getUri(), $this->prefix) === false) {
/* Skip if prefix doesn't match */
if ($this->prefix !== null && stripos($url, $this->prefix) === false) {
return false;
}
return $this->matchDomain($request);
}
/**
* Add exception handler
*
* @param IExceptionHandler|string $handler
* @return static $this
*/
public function addExceptionHandler($handler)
{
$this->exceptionHandlers[] = $handler;
return $this;
}
/**
* Set exception-handlers for group
*
@@ -133,24 +146,27 @@ class RouteGroup extends Route implements IGroupRoute
public function setSettings(array $values, $merge = false)
{
if (isset($values['prefix'])) {
if (isset($values['prefix']) === true) {
$this->setPrefix($values['prefix'] . $this->prefix);
}
if (isset($values['exceptionHandler'])) {
if ($merge === false && isset($values['exceptionHandler']) === true) {
$this->setExceptionHandlers((array)$values['exceptionHandler']);
}
if (isset($values['domain'])) {
if ($merge === false && isset($values['domain']) === true) {
$this->setDomains((array)$values['domain']);
}
if (isset($values['as'])) {
if (isset($values['as']) === true) {
$name = $values['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);
@@ -175,6 +191,10 @@ class RouteGroup extends Route implements IGroupRoute
$values['as'] = $this->name;
}
if (count($this->parameters) !== 0) {
$values['parameters'] = $this->parameters;
}
return array_merge($values, parent::toArray());
}
@@ -0,0 +1,36 @@
<?php
namespace Pecee\SimpleRouter\Route;
use Pecee\Http\Request;
class RoutePartialGroup extends RouteGroup implements IPartialGroupRoute
{
protected $urlRegex = '/^%s\/?/u';
/**
* Method called to check if route matches
*
* @param string $url
* @param Request $request
* @return bool
*/
public function matchRoute($url, Request $request)
{
if ($this->prefix !== null) {
/* Parse parameters from current route */
$parameters = $this->parseParameters($this->prefix, $url);
/* If no custom regular expression or parameters was found on this route, we stop */
if ($parameters === null) {
return false;
}
/* Set the parameters */
$this->setParameters((array)$parameters);
}
return $this->matchDomain($request);
}
}
+98 -79
View File
@@ -1,8 +1,8 @@
<?php
namespace Pecee\SimpleRouter\Route;
use Pecee\Http\Request;
use Pecee\SimpleRouter\Exceptions\NotFoundHttpException;
class RouteResource extends LoadableRoute implements IControllerRoute
{
@@ -15,6 +15,17 @@ class RouteResource extends LoadableRoute implements IControllerRoute
'update' => '',
'destroy' => '',
];
protected $methodNames = [
'index' => 'index',
'create' => 'create',
'store' => 'store',
'show' => 'show',
'edit' => 'edit',
'update' => 'update',
'destroy' => 'destroy',
];
protected $names = [];
protected $controller;
@@ -42,8 +53,8 @@ class RouteResource extends LoadableRoute implements IControllerRoute
}
/* Remove method/type */
if (stripos($name, '.') !== false) {
$name = substr($name, 0, strrpos($name, '.'));
if (strpos($name, '.') !== false) {
$name = (string)substr($name, 0, strrpos($name, '.'));
}
return (strtolower($this->name) === strtolower($name));
@@ -51,99 +62,80 @@ class RouteResource extends LoadableRoute implements IControllerRoute
public function findUrl($method = null, $parameters = null, $name = null)
{
$method = array_search($name, $this->names);
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;
}
public function renderRoute(Request $request)
{
if ($this->getCallback() !== null && is_callable($this->getCallback())) {
// 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 = strtolower($controller[1]);
if (!method_exists($class, $method)) {
throw new NotFoundHttpException(sprintf('Method %s does not exist in class %s', $method, $className), 404);
}
call_user_func_array([$class, $method], $this->getParameters());
return $class;
}
return null;
}
protected function call($method, $parameters)
protected function call($method)
{
$this->setCallback($this->controller . '@' . $method);
$this->parameters = $parameters;
return true;
}
public function matchRoute(Request $request)
public function matchRoute($url, Request $request)
{
$url = parse_url(urldecode($request->getUri()), PHP_URL_PATH);
$url = rtrim($url, '/') . '/';
/* Match global regular-expression for route */
$regexMatch = $this->matchRegex($request, $url);
if ($regexMatch === false || (stripos($url, $this->url) !== 0 && strtolower($url) !== strtolower($this->url))) {
return false;
}
$route = rtrim($this->url, '/') . '/{id?}/{action?}';
$parameters = $this->parseParameters($route, $url);
/* Parse parameters from current route */
$this->parameters = $this->parseParameters($route, $url);
if ($parameters !== null) {
$parameters = array_merge($this->parameters, (array)$parameters);
$action = isset($parameters['action']) ? $parameters['action'] : null;
unset($parameters['action']);
$method = $request->getMethod();
// Delete
if (isset($parameters['id']) && $method === static::REQUEST_TYPE_DELETE) {
return $this->call('destroy', $parameters);
}
// Update
if (isset($parameters['id']) && in_array($method, [static::REQUEST_TYPE_PATCH, static::REQUEST_TYPE_PUT])) {
return $this->call('update', $parameters);
}
// Edit
if (isset($parameters['id']) && strtolower($action) === 'edit' && $method === static::REQUEST_TYPE_GET) {
return $this->call('edit', $parameters);
}
// Create
if (strtolower($action) === 'create' && $method === static::REQUEST_TYPE_GET) {
return $this->call('create', $parameters);
}
// Save
if ($method === static::REQUEST_TYPE_POST) {
return $this->call('store', $parameters);
}
// Show
if (isset($parameters['id']) && $method === static::REQUEST_TYPE_GET) {
return $this->call('show', $parameters);
}
// Index
return $this->call('index', $parameters);
/* If no custom regular expression or parameters was found on this route, we stop */
if ($regexMatch === null && $this->parameters === null) {
return false;
}
return null;
$action = strtolower(trim($this->parameters['action']));
$id = $this->parameters['id'];
// Remove action parameter
unset($this->parameters['action']);
$method = $request->getMethod();
// Delete
if ($method === static::REQUEST_TYPE_DELETE && $id !== null) {
return $this->call($this->methodNames['destroy']);
}
// Update
if ($id !== null && in_array($method, [static::REQUEST_TYPE_PATCH, static::REQUEST_TYPE_PUT], false) === true) {
return $this->call($this->methodNames['update']);
}
// Edit
if ($method === static::REQUEST_TYPE_GET && $id !== null && $action === 'edit') {
return $this->call($this->methodNames['edit']);
}
// Create
if ($method === static::REQUEST_TYPE_GET && $id === 'create') {
return $this->call($this->methodNames['create']);
}
// Save
if ($method === static::REQUEST_TYPE_POST) {
return $this->call($this->methodNames['store']);
}
// Show
if ($method === static::REQUEST_TYPE_GET && $id !== null) {
return $this->call($this->methodNames['show']);
}
// Index
return $this->call($this->methodNames['index']);
}
/**
@@ -182,6 +174,29 @@ class RouteResource extends LoadableRoute implements IControllerRoute
return $this;
}
/**
* Define custom method name for resource controller
*
* @param array $names
* @return static $this
*/
public function setMethodNames(array $names)
{
$this->methodNames = $names;
return $this;
}
/**
* Get method names
*
* @return array $this
*/
public function getMethodNames()
{
return $this->methodNames;
}
/**
* Merge with information from another route.
*
@@ -191,10 +206,14 @@ class RouteResource extends LoadableRoute implements IControllerRoute
*/
public function setSettings(array $values, $merge = false)
{
if (isset($values['names'])) {
if (isset($values['names']) === true) {
$this->names = $values['names'];
}
if (isset($values['methods']) === true) {
$this->methodNames = $values['methods'];
}
parent::setSettings($values, $merge);
return $this;
+15 -26
View File
@@ -1,4 +1,5 @@
<?php
namespace Pecee\SimpleRouter\Route;
use Pecee\Http\Request;
@@ -11,39 +12,27 @@ class RouteUrl extends LoadableRoute
$this->setCallback($callback);
}
public function matchRoute(Request $request)
public function matchRoute($url, Request $request)
{
$url = parse_url(urldecode($request->getUri()), PHP_URL_PATH);
$url = rtrim($url, '/') . '/';
/* Match global regular-expression for route */
$regexMatch = $this->matchRegex($request, $url);
// Match on custom defined regular expression
if ($this->regex !== null) {
$parameters = [];
if (preg_match($this->regex, $request->getHost() . $url, $parameters)) {
/* Remove global match */
if (count($parameters) > 1) {
array_shift($parameters);
$this->parameters = $parameters;
}
return true;
}
return null;
if ($regexMatch === false) {
return false;
}
// Make regular expression based on route
$route = rtrim($this->url, '/') . '/';
/* Parse parameters from current route */
$parameters = $this->parseParameters($this->url, $url);
$parameters = $this->parseParameters($route, $url);
if ($parameters !== null) {
$this->parameters = array_merge($this->parameters, $parameters);
return true;
/* If no custom regular expression or parameters was found on this route, we stop */
if ($regexMatch === null && $parameters === null) {
return false;
}
return null;
/* Set the parameters */
$this->setParameters((array)$parameters);
return true;
}
}
+201 -139
View File
@@ -1,4 +1,5 @@
<?php
namespace Pecee\SimpleRouter;
use Pecee\Handlers\IExceptionHandler;
@@ -9,6 +10,7 @@ use Pecee\SimpleRouter\Exceptions\NotFoundHttpException;
use Pecee\SimpleRouter\Route\IControllerRoute;
use Pecee\SimpleRouter\Route\IGroupRoute;
use Pecee\SimpleRouter\Route\ILoadableRoute;
use Pecee\SimpleRouter\Route\IPartialGroupRoute;
use Pecee\SimpleRouter\Route\IRoute;
class Router
@@ -69,37 +71,6 @@ class Router
*/
protected $exceptionHandlers;
/**
* The current loaded route
* @var ILoadableRoute|null
*/
protected $loadedRoute;
/**
* List over route changes (to avoid endless-looping)
* @var array
*/
protected $routeRewrites = [];
/**
* If the route has been rewritten/changed this property will contain the original url.
* @var string
*/
protected $originalUrl;
/**
* Get current router instance
* @return static
*/
public static function getInstance()
{
if (static::$instance === null) {
static::$instance = new static();
}
return static::$instance;
}
public function __construct()
{
$this->reset();
@@ -137,44 +108,27 @@ class Router
return $route;
}
/**
* Process added routes.
*
* @param array $routes
* @param IGroupRoute|null $group
* @param IRoute|null $parent
* @throws NotFoundHttpException
*/
protected function processRoutes(array $routes, IGroupRoute $group = null, IRoute $parent = null)
{
// Loop through each route-request
$max = count($routes) - 1;
/* @var $route IRoute */
$exceptionHandlers = [];
$url = ($this->request->getRewriteUrl() !== null) ? $this->request->getRewriteUrl() : $this->request->getUri()->getPath();
for ($i = $max; $i >= 0; $i--) {
$route = $routes[$i];
if ($route instanceof IGroupRoute) {
$group = $route;
if ($route->getCallback() !== null && is_callable($route->getCallback())) {
$this->processingRoute = true;
$route->renderRoute($this->request);
$this->processingRoute = false;
if ($route->matchRoute($this->request)) {
/* Add exceptionhandlers */
if (count($route->getExceptionHandlers()) > 0) {
$this->exceptionHandlers = array_merge($route->getExceptionHandlers(), $this->exceptionHandlers);
}
}
}
}
if ($group !== null) {
/* Add the parent group */
$route->setGroup($group);
}
if ($parent !== null) {
/* Add the parent route */
@@ -185,15 +139,50 @@ class Router
}
if ($group !== null) {
/* Add the parent group */
$route->setGroup($group);
}
/* @var $route IGroupRoute */
if ($route instanceof IGroupRoute) {
$group = $route;
if ($route->matchRoute($url, $this->request) === true) {
/* Add exception handlers */
if (count($route->getExceptionHandlers()) !== 0) {
/** @noinspection AdditionOperationOnArraysInspection */
$exceptionHandlers += $route->getExceptionHandlers();
}
/* Only render partial group if it matches */
if ($route instanceof IPartialGroupRoute) {
$this->processingRoute = true;
$route->renderRoute($this->request);
$this->processingRoute = false;
}
}
if (($route instanceof IPartialGroupRoute) === false) {
$this->processingRoute = true;
$route->renderRoute($this->request);
$this->processingRoute = false;
}
}
if ($route instanceof ILoadableRoute) {
/* Add the route to the map, so we can find the active one when all routes has been loaded */
$this->processedRoutes[] = $route;
}
if (count($this->routeStack) > 0) {
if (count($this->routeStack) !== 0) {
/* Pop and grap the routes added when executing group callback earlier */
/* Pop and grab the routes added when executing group callback earlier */
$stack = $this->routeStack;
$this->routeStack = [];
@@ -201,80 +190,100 @@ class Router
$this->processRoutes($stack, $route, $group);
}
}
$this->exceptionHandlers = array_merge($exceptionHandlers, $this->exceptionHandlers);
}
/**
* Load routes
* @throws NotFoundHttpException
* @return void
*/
public function loadRoutes()
{
/* Initialize boot-managers */
if (count($this->bootManagers) !== 0) {
$max = count($this->bootManagers) - 1;
/* @var $manager IRouterBootManager */
for ($i = $max; $i >= 0; $i--) {
$manager = $this->bootManagers[$i];
$manager->boot($this->request);
}
}
/* Loop through each route-request */
$this->processRoutes($this->routes);
}
/**
* Routes the request
*
* @param bool $rewrite
* @return string|mixed
* @throws HttpException
* @throws \Exception
*/
public function routeRequest($rewrite = false)
{
$this->loadedRoute = null;
$routeNotAllowed = false;
try {
/* Initialize boot-managers */
if (count($this->bootManagers) > 0) {
$max = count($this->bootManagers) - 1;
/* @var $manager IRouterBootManager */
for ($i = $max; $i >= 0; $i--) {
$manager = $this->bootManagers[$i];
$this->request = $manager->boot($this->request);
if (!($this->request instanceof Request)) {
throw new HttpException('Bootmanager "' . get_class($manager) . '" must return instance of ' . Request::class, 500);
}
}
}
if ($rewrite === false) {
/* Loop through each route-request */
$this->processRoutes($this->routes);
$this->loadRoutes();
if ($this->csrfVerifier !== null) {
// Verify csrf token for request
/* Verify csrf token for request */
$this->csrfVerifier->handle($this->request);
}
$this->originalUrl = $this->request->getUri();
}
$url = ($this->request->getRewriteUrl() !== null) ? $this->request->getRewriteUrl() : $this->request->getUri()->getPath();
$max = count($this->processedRoutes) - 1;
/* @var $route IRoute */
/* @var $route ILoadableRoute */
for ($i = $max; $i >= 0; $i--) {
$route = $this->processedRoutes[$i];
/* If the route matches */
if ($route->matchRoute($this->request)) {
if ($route->matchRoute($url, $this->request) === true) {
/* Check if request method matches */
if (count($route->getRequestMethods()) > 0 && !in_array($this->request->getMethod(), $route->getRequestMethods())) {
if (count($route->getRequestMethods()) !== 0 && in_array($this->request->getMethod(), $route->getRequestMethods(), false) === false) {
$routeNotAllowed = true;
continue;
}
$this->loadedRoute = $route;
$this->loadedRoute->loadMiddleware($this->request, $this->loadedRoute);
$route->loadMiddleware($this->request);
/* If the request has changed, we reinitialize the router */
if ($this->request->getUri() !== $this->originalUrl && !in_array($this->request->getUri(), $this->routeRewrites)) {
$this->routeRewrites[] = $this->request->getUri();
$this->routeRequest(true);
$rewriteRoute = $this->request->getRewriteRoute();
return;
if ($rewriteRoute !== null) {
$rewriteRoute->loadMiddleware($this->request);
return $rewriteRoute->renderRoute($this->request);
}
/* If the request has changed */
$rewriteUrl = $this->request->getRewriteUrl();
if ($rewriteUrl !== null && $rewriteUrl !== $url) {
unset($this->processedRoutes[$i]);
$this->processedRoutes = array_values($this->processedRoutes);
return $this->routeRequest(true);
}
/* Render route */
$routeNotAllowed = false;
$this->request->setUri($this->originalUrl);
$this->loadedRoute->renderRoute($this->request);
$this->request->setLoadedRoute($route);
break;
return $route->renderRoute($this->request);
}
}
@@ -283,38 +292,72 @@ class Router
}
if ($routeNotAllowed === true) {
$this->handleException(new HttpException('Route or method not allowed', 403));
$message = sprintf('Route "%s" or method "%s" not allowed.', $this->request->getUri()->getPath(), $this->request->getMethod());
$this->handleException(new HttpException($message, 403));
}
if ($this->loadedRoute === null) {
$this->handleException(new NotFoundHttpException('Route not found: ' . $this->request->getUri(), 404));
if ($this->request->getLoadedRoute() === null) {
$rewriteUrl = $this->request->getRewriteUrl();
if ($rewriteUrl !== null) {
$message = sprintf('Route not found: "%s" (rewrite from: "%s")', $rewriteUrl, $this->request->getUri()->getPath());
} else {
$message = sprintf('Route not found: "%s"', $this->request->getUri()->getPath());
}
$this->handleException(new NotFoundHttpException($message, 404));
}
return null;
}
/**
* @param \Exception $e
* @throws HttpException
* @throws \Exception
* @return string
*/
protected function handleException(\Exception $e)
{
$max = count($this->exceptionHandlers);
$url = ($this->request->getRewriteUrl() !== null) ? $this->request->getRewriteUrl() : $this->request->getUri()->getPath();
/* @var $handler IExceptionHandler */
for ($i = 0; $i < $max; $i++) {
foreach ($this->exceptionHandlers as $key => $handler) {
$handler = $this->exceptionHandlers[$i];
if (is_object($handler) === false) {
$handler = new $handler();
}
$handler = new $handler();
if (!($handler instanceof IExceptionHandler)) {
if (($handler instanceof IExceptionHandler) === false) {
throw new HttpException('Exception handler must implement the IExceptionHandler interface.', 500);
}
$request = $handler->handleError($this->request, $this->loadedRoute, $e);
try {
/* If the request has changed */
if ($request !== null && $this->request->getUri() !== $this->originalUrl && !in_array($request->getUri(), $this->routeRewrites)) {
$this->request = $request;
$this->routeRewrites[] = $request->getUri();
$this->routeRequest(true);
if ($handler->handleError($this->request, $e) !== null) {
$rewriteRoute = $this->request->getRewriteRoute();
if ($rewriteRoute !== null) {
$rewriteRoute->loadMiddleware($this->request);
return $rewriteRoute->renderRoute($this->request);
}
$rewriteUrl = $this->request->getRewriteUrl();
/* If the request has changed */
if ($rewriteUrl !== null && $rewriteUrl !== $url) {
unset($this->exceptionHandlers[$key]);
$this->exceptionHandlers = array_values($this->exceptionHandlers);
return $this->routeRequest(true);
}
}
} catch (\Exception $e) {
return;
}
}
@@ -323,11 +366,11 @@ class Router
public function arrayToParams(array $getParams = [], $includeEmpty = true)
{
if (count($getParams) > 0) {
if (count($getParams) !== 0) {
if ($includeEmpty === false) {
$getParams = array_filter($getParams, function ($item) {
return (!empty($item));
return (trim($item) !== '');
});
}
@@ -363,7 +406,7 @@ class Router
}
/* Using @ is most definitely a controller@method or alias@method */
if (strpos($name, '@') !== false) {
if (is_string($name) === true && strpos($name, '@') !== false) {
list($controller, $method) = array_map('strtolower', explode('@', $name));
if ($controller === strtolower($route->getClass()) && $method === strtolower($route->getMethod())) {
@@ -372,10 +415,10 @@ class Router
}
/* Check if callback matches (if it's not a function) */
if (strpos($name, '@') !== false && strpos($route->getCallback(), '@') !== false && !is_callable($route->getCallback())) {
if (is_string($name) === true && is_string($route->getCallback()) && strpos($name, '@') !== false && strpos($route->getCallback(), '@') !== false && is_callable($route->getCallback()) === false) {
/* Check if the entire callback is matching */
if (strtolower($route->getCallback()) === strtolower($name) || strpos($route->getCallback(), $name) === 0) {
if (strpos($route->getCallback(), $name) === 0 || strtolower($route->getCallback()) === strtolower($name)) {
return $route;
}
@@ -404,6 +447,7 @@ class Router
* @param string|null $name
* @param string|array|null $parameters
* @param array|null $getParams
* @throws \InvalidArgumentException
* @return string
*/
public function getUrl($name = null, $parameters = null, $getParams = null)
@@ -412,6 +456,10 @@ class Router
throw new \InvalidArgumentException('Invalid type for getParams. Must be array or null');
}
if ($name === '' && $parameters === '') {
return '/';
}
/* Only merge $_GET when all parameters are null */
if ($name === null && $parameters === null && $getParams === null) {
$getParams = $_GET;
@@ -421,14 +469,14 @@ class Router
/* Return current route if no options has been specified */
if ($name === null && $parameters === null) {
$url = rtrim(parse_url($this->request->getUri(), PHP_URL_PATH), '/');
return (($url === '') ? '/' : $url . '/') . $this->arrayToParams($getParams);
return $this->request->getUri()->getPath() . $this->arrayToParams($getParams);
}
$loadedRoute = $this->request->getLoadedRoute();
/* If nothing is defined and a route is loaded we use that */
if ($name === null && $this->loadedRoute !== null) {
return $this->loadedRoute->findUrl($this->loadedRoute->getMethod(), $parameters, $name) . $this->arrayToParams($getParams);
if ($name === null && $loadedRoute !== null) {
return $loadedRoute->findUrl($loadedRoute->getMethod(), $parameters, $name) . $this->arrayToParams($getParams);
}
/* We try to find a match on the given name */
@@ -439,7 +487,7 @@ class Router
}
/* Using @ is most definitely a controller@method or alias@method */
if (stripos($name, '@') !== false) {
if (is_string($name) === true && strpos($name, '@') !== false) {
list($controller, $method) = explode('@', $name);
/* Loop through all the routes to see if we can find a match */
@@ -452,7 +500,7 @@ class Router
$route = $this->processedRoutes[$i];
/* Check if the route contains the name/alias */
if ($route->hasName($controller)) {
if ($route->hasName($controller) === true) {
return $route->findUrl($method, $parameters, $name) . $this->arrayToParams($getParams);
}
@@ -465,7 +513,7 @@ class Router
}
/* No result so we assume that someone is using a hardcoded url and join everything together. */
$url = trim(join('/', array_merge((array)$name, (array)$parameters)), '/');
$url = trim(implode('/', array_merge((array)$name, (array)$parameters)), '/');
return (($url === '') ? '/' : '/' . $url . '/') . $this->arrayToParams($getParams);
}
@@ -497,6 +545,16 @@ class Router
$this->bootManagers[] = $bootManager;
}
/**
* Get routes that has been processed.
*
* @return array
*/
public function getProcessedRoutes()
{
return $this->processedRoutes;
}
/**
* @return array
*/
@@ -505,6 +563,19 @@ class Router
return $this->routes;
}
/**
* Set routes
*
* @param array $routes
* @return static $this
*/
public function setRoutes(array $routes)
{
$this->routes = $routes;
return $this;
}
/**
* Get current request
*
@@ -537,13 +608,4 @@ class Router
return $this;
}
/**
* Get loaded route
* @return ILoadableRoute|null
*/
public function getLoadedRoute()
{
return $this->loadedRoute;
}
}
}
+96 -14
View File
@@ -7,13 +7,16 @@
* 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\Handlers\CallbackExceptionHandler;
use Pecee\Http\Middleware\BaseCsrfVerifier;
use Pecee\Http\Response;
use Pecee\SimpleRouter\Exceptions\HttpException;
use Pecee\SimpleRouter\Exceptions\NotFoundHttpException;
use Pecee\SimpleRouter\Route\IRoute;
use Pecee\SimpleRouter\Route\RoutePartialGroup;
use Pecee\SimpleRouter\Route\RouteController;
use Pecee\SimpleRouter\Route\RouteGroup;
use Pecee\SimpleRouter\Route\RouteResource;
@@ -33,15 +36,20 @@ class SimpleRouter
*/
protected static $response;
/**
* Router instance
* @var Router
*/
protected static $router;
/**
* Start/route request
*
* @throws HttpException
* @throws NotFoundHttpException
* @throws HttpException|NotFoundHttpException|\Exception
*/
public static function start()
{
static::router()->routeRequest();
echo static::router()->routeRequest();
}
/**
@@ -163,14 +171,41 @@ class SimpleRouter
*/
public static function group(array $settings = [], \Closure $callback)
{
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);
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 array $settings
* @param \Closure $callback
* @throws \InvalidArgumentException
* @return RoutePartialGroup
*/
public static function partialGroup($url, \Closure $callback, array $settings = [])
{
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;
@@ -212,7 +247,7 @@ class SimpleRouter
* @param string $url
* @param string|\Closure $callback
* @param array|null $settings
* @return RouteUrl
* @return RouteUrl|IRoute
*/
public static function match(array $requestMethods, $url, $callback, array $settings = null)
{
@@ -235,7 +270,7 @@ class SimpleRouter
* @param string $url
* @param string|\Closure $callback
* @param array|null $settings
* @return RouteUrl
* @return RouteUrl|IRoute
*/
public static function all($url, $callback, array $settings = null)
{
@@ -257,7 +292,7 @@ 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)
{
@@ -279,11 +314,12 @@ 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)
{
$route = new RouteResource($url, $controller);
$route = static::addDefaultNamespace($route);
if ($settings !== null) {
$route->setSettings($settings);
@@ -294,6 +330,28 @@ class SimpleRouter
return $route;
}
/**
* Add exception callback handler.
*
* @param \Closure $callback
* @return CallbackExceptionHandler $callbackHandler
*/
public static function error(\Closure $callback)
{
$routes = static::router()->getRoutes();
$callbackHandler = new CallbackExceptionHandler($callback);
$group = new RouteGroup();
$group->addExceptionHandler($callbackHandler);
array_unshift($routes, $group);
static::router()->setRoutes($routes);
return $callbackHandler;
}
/**
* Get url for a route by using either name/alias, class or method name.
*
@@ -309,6 +367,7 @@ class SimpleRouter
* @param string|null $name
* @param string|array|null $parameters
* @param array|null $getParams
* @throws \Exception
* @return string
*/
public static function getUrl($name = null, $parameters = null, $getParams = null)
@@ -347,7 +406,11 @@ class SimpleRouter
*/
public static function router()
{
return Router::getInstance();
if (static::$router === null) {
static::$router = new Router();
}
return static::$router;
}
/**
@@ -356,19 +419,38 @@ class SimpleRouter
* @param IRoute $route
* @return IRoute
*/
protected static function addDefaultNamespace(IRoute $route)
public static function addDefaultNamespace(IRoute $route)
{
if (static::$defaultNamespace !== null) {
$namespace = static::$defaultNamespace;
if ($route->getNamespace() !== null) {
$namespace .= '\\' . $route->getNamespace();
$callback = $route->getCallback();
/* Only add default namespace on relative callbacks */
if ($callback === null || (is_string($callback) === true && $callback[0] !== '\\')) {
$namespace = static::$defaultNamespace;
$currentNamespace = $route->getNamespace();
if ($currentNamespace !== null) {
$namespace .= '\\' . $currentNamespace;
}
$route->setDefaultNamespace($namespace);
}
$route->setDefaultNamespace($namespace);
}
return $route;
}
/**
* Get default namespace
* @return string
*/
public static function getDefaultNamespace()
{
return static::$defaultNamespace;
}
}
+20 -10
View File
@@ -2,24 +2,34 @@
class DummyController
{
public function start()
public function method1()
{
echo static::class . '@' . 'start() OK';
}
public function method2()
{
}
public function param($params = null)
{
$params = func_get_args();
echo 'Params: ' . join(', ', $params);
echo join(', ', func_get_args());
}
public function notFound()
{
echo 'not found';
}
public function getTest()
{
echo 'getTest';
}
public function silent() {
public function postTest()
{
echo 'postTest';
}
}
public function putTest()
{
echo 'putTest';
}
}
+2 -3
View File
@@ -1,12 +1,11 @@
<?php
require_once 'Exceptions/MiddlewareLoadedException.php';
use Pecee\Http\Middleware\IMiddleware;
use Pecee\Http\Request;
class DummyMiddleware implements IMiddleware
class DummyMiddleware implements \Pecee\Http\Middleware\IMiddleware
{
public function handle(Request $request, \Pecee\SimpleRouter\Route\ILoadableRoute &$route)
public function handle(Request $request)
{
throw new MiddlewareLoadedException('Middleware loaded!');
}
@@ -0,0 +1,4 @@
<?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;
}
}
+2 -2
View File
@@ -2,9 +2,9 @@
class ExceptionHandler implements \Pecee\Handlers\IExceptionHandler
{
public function handleError(\Pecee\Http\Request $request, \Pecee\SimpleRouter\Route\ILoadableRoute &$route = null, \Exception $error)
public function handleError(\Pecee\Http\Request $request, \Exception $error)
{
throw $error;
echo $error->getMessage();
}
}
@@ -0,0 +1,14 @@
<?php
class ExceptionHandlerFirst implements \Pecee\Handlers\IExceptionHandler
{
public function handleError(\Pecee\Http\Request $request, \Exception $error)
{
global $stack;
$stack[] = static::class;
$request->setUri(new \Pecee\Http\Uri('/'));
return $request;
}
}
@@ -0,0 +1,14 @@
<?php
class ExceptionHandlerSecond implements \Pecee\Handlers\IExceptionHandler
{
public function handleError(\Pecee\Http\Request $request, \Exception $error)
{
global $stack;
$stack[] = static::class;
$request->setUri(new \Pecee\Http\Uri('/'));
return $request;
}
}
@@ -0,0 +1,13 @@
<?php
class ExceptionHandlerThird implements \Pecee\Handlers\IExceptionHandler
{
public function handleError(\Pecee\Http\Request $request, \Exception $error)
{
global $stack;
$stack[] = static::class;
throw new ResponseException('ExceptionHandler loaded');
}
}
+39
View File
@@ -0,0 +1,39 @@
<?php
class ResourceController implements \Pecee\Controllers\IResourceController
{
public function index()
{
echo 'index';
}
public function show($id)
{
echo 'show ' . $id;
}
public function store()
{
echo 'store';
}
public function create()
{
echo 'create';
}
public function edit($id)
{
echo 'edit ' . $id;
}
public function update($id)
{
echo 'update ' . $id;
}
public function destroy($id)
{
echo 'destroy ' . $id;
}
}
+49 -61
View File
@@ -2,94 +2,82 @@
require_once 'Dummy/DummyMiddleware.php';
require_once 'Dummy/DummyController.php';
use Pecee\SimpleRouter\SimpleRouter as SimpleRouter;
require_once 'Helpers/TestRouter.php';
class GroupTest extends PHPUnit_Framework_TestCase
{
protected $result;
protected $result;
public function testGroupLoad()
{
$this->result = false;
public function testGroupLoad()
{
$this->result = false;
SimpleRouter::group(['prefix' => '/group'], function () {
$this->result = true;
});
TestRouter::group(['prefix' => '/group'], function () {
$this->result = true;
});
try {
SimpleRouter::start();
} catch (Exception $e) {
// ignore RouteNotFound exception
}
try {
TestRouter::debug('/', 'get');
} catch(\Exception $e) {
$this->assertTrue($this->result);
}
}
$this->assertTrue($this->result);
}
public function testNestedGroup()
{
public function testNestedGroup()
{
SimpleRouter::router()->reset();
SimpleRouter::request()->setUri('/api/v1/test');
SimpleRouter::request()->setMethod('get');
TestRouter::group(['prefix' => '/api'], function () {
SimpleRouter::group(['prefix' => '/api'], function () {
TestRouter::group(['prefix' => '/v1'], function () {
TestRouter::get('/test', 'DummyController@method1');
});
SimpleRouter::group(['prefix' => '/v1'], function () {
SimpleRouter::get('/test', 'DummyController@start');
});
});
});
TestRouter::debug('/api/v1/test', 'get');
SimpleRouter::start();
}
}
public function testManyRoutes()
{
public function testMultipleRoutes()
{
SimpleRouter::router()->reset();
SimpleRouter::request()->setUri('/my/match');
SimpleRouter::request()->setMethod('get');
TestRouter::group(['prefix' => '/api'], function () {
SimpleRouter::group(['prefix' => '/api'], function () {
TestRouter::group(['prefix' => '/v1'], function () {
TestRouter::get('/test', 'DummyController@method1');
});
SimpleRouter::group(['prefix' => '/v1'], function () {
SimpleRouter::get('/test', 'DummyController@start');
});
});
});
TestRouter::get('/my/match', 'DummyController@method1');
SimpleRouter::get('/my/match', 'DummyController@start');
TestRouter::group(['prefix' => '/service'], function () {
SimpleRouter::group(['prefix' => '/service'], function () {
TestRouter::group(['prefix' => '/v1'], function () {
TestRouter::get('/no-match', 'DummyController@method1');
});
SimpleRouter::group(['prefix' => '/v1'], function () {
SimpleRouter::get('/no-match', 'DummyController@start');
});
});
});
TestRouter::debug('/my/match', 'get');
}
SimpleRouter::start();
}
public function testUrls()
{
// Test array name
TestRouter::get('/my/fancy/url/1', 'DummyController@method1', ['as' => 'fancy1']);
public function testUrls()
{
// Test method name
TestRouter::get('/my/fancy/url/2', 'DummyController@method1')->setName('fancy2');
SimpleRouter::router()->reset();
SimpleRouter::request()->setUri('/my/fancy/url/1');
SimpleRouter::request()->setMethod('get');
TestRouter::debugNoReset('/my/fancy/url/1');
// Test array name
SimpleRouter::get('/my/fancy/url/1', 'DummyController@start', ['as' => 'fancy1']);
$this->assertEquals('/my/fancy/url/1/', TestRouter::getUrl('fancy1'));
$this->assertEquals('/my/fancy/url/2/', TestRouter::getUrl('fancy2'));
// Test method name
SimpleRouter::get('/my/fancy/url/2', 'DummyController@start')->setName('fancy2');
TestRouter::router()->reset();
SimpleRouter::start();
$this->assertEquals('/my/fancy/url/1/', SimpleRouter::getUrl('fancy1'));
$this->assertEquals('/my/fancy/url/2/', SimpleRouter::getUrl('fancy2'));
}
}
}
+41
View File
@@ -0,0 +1,41 @@
<?php
class TestRouter extends \Pecee\SimpleRouter\SimpleRouter
{
public static function debugNoReset($testUri, $testMethod = 'get')
{
static::request()->setUri(new \Pecee\Http\Uri($testUri));
static::request()->setMethod($testMethod);
static::start();
}
public static function debug($testUri, $testMethod = 'get')
{
try {
static::debugNoReset($testUri, $testMethod);
} catch(\Exception $e) {
static::router()->reset();
throw $e;
}
static::router()->reset();
}
public static function debugOutput($testUri, $testMethod = 'get')
{
$response = null;
// Route request
ob_start();
static::debug($testUri, $testMethod);
$response = ob_get_contents();
ob_end_clean();
// Return response
return $response;
}
}
+20 -18
View File
@@ -3,30 +3,32 @@
require_once 'Dummy/DummyMiddleware.php';
require_once 'Dummy/DummyController.php';
require_once 'Dummy/Handler/ExceptionHandler.php';
use Pecee\SimpleRouter\SimpleRouter as SimpleRouter;
require_once 'Helpers/TestRouter.php';
class MiddlewareTest extends PHPUnit_Framework_TestCase
{
public function testMiddlewareFound()
{
SimpleRouter::router()->reset();
SimpleRouter::request()->setMethod('get');
SimpleRouter::request()->setUri('/my/test/url');
public function testMiddlewareFound()
{
$this->setExpectedException(MiddlewareLoadedException::class);
SimpleRouter::group(['exceptionHandler' => 'ExceptionHandler'], function () {
SimpleRouter::get('/my/test/url', 'DummyController@start', ['middleware' => 'DummyMiddleware']);
});
TestRouter::group(['exceptionHandler' => 'ExceptionHandler'], function () {
TestRouter::get('/my/test/url', 'DummyController@method1', ['middleware' => 'DummyMiddleware']);
});
$found = false;
TestRouter::debug('/my/test/url', 'get');
try {
SimpleRouter::start();
} catch (\Exception $e) {
$found = ($e instanceof MiddlewareLoadedException);
}
}
$this->assertTrue($found);
}
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');
}
}
@@ -0,0 +1,27 @@
<?php
require_once 'Dummy/DummyMiddleware.php';
require_once 'Dummy/DummyController.php';
require_once 'Dummy/Exceptions/ExceptionHandlerException.php';
require_once 'Helpers/TestRouter.php';
class RouterCallbackExceptionHandlerTest extends PHPUnit_Framework_TestCase
{
public function testCallbackExceptionHandler()
{
$this->setExpectedException(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();
}
}
+42
View File
@@ -0,0 +1,42 @@
<?php
require_once 'Dummy/DummyController.php';
require_once 'Helpers/TestRouter.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);
}
}
+29
View File
@@ -0,0 +1,29 @@
<?php
require_once 'Dummy/DummyMiddleware.php';
require_once 'Dummy/DummyController.php';
require_once 'Dummy/Handler/ExceptionHandler.php';
require_once 'Helpers/TestRouter.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);
}
}
+70
View File
@@ -0,0 +1,70 @@
<?php
require_once 'Dummy/ResourceController.php';
require_once 'Helpers/TestRouter.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);
}
}
+78
View File
@@ -0,0 +1,78 @@
<?php
require_once 'Dummy/DummyController.php';
require_once 'Dummy/Exceptions/ResponseException.php';
require_once 'Dummy/Handler/ExceptionHandlerFirst.php';
require_once 'Dummy/Handler/ExceptionHandlerSecond.php';
require_once 'Dummy/Handler/ExceptionHandlerThird.php';
require_once 'Helpers/TestRouter.php';
class RouteRewriteTest 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 () {
TestRouter::group(['exceptionHandler' => ExceptionHandlerThird::class], function () {
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->setExpectedException(\Pecee\SimpleRouter\Exceptions\NotFoundHttpException::class);
TestRouter::error(function (\Pecee\Http\Request $request, \Exception $error) {
if (strtolower($request->getUri()->getPath()) == '/my/test') {
$request->setRewriteUrl('/another-non-existing');
return $request;
}
});
TestRouter::debug('/my/test', 'get');
}
}
+124 -104
View File
@@ -2,140 +2,160 @@
require_once 'Dummy/DummyMiddleware.php';
require_once 'Dummy/DummyController.php';
require_once 'Dummy/Handler/ExceptionHandler.php';
use Pecee\SimpleRouter\SimpleRouter as SimpleRouter;
use Pecee\SimpleRouter\Exceptions\NotFoundHttpException as NotFoundHttpException;
require_once 'Dummy/Exceptions/ExceptionHandlerException.php';
require_once 'Helpers/TestRouter.php';
class RouterRouteTest extends PHPUnit_Framework_TestCase
{
protected $result = false;
protected $result = false;
public function testNotFound()
{
SimpleRouter::router()->reset();
SimpleRouter::request()->setMethod('get');
SimpleRouter::request()->setUri('/test-param1-param2');
public function testMultiParam()
{
TestRouter::get('/test-{param1}-{param2}', function ($param1, $param2) {
SimpleRouter::group(['exceptionHandler' => 'ExceptionHandler'], function () {
SimpleRouter::get('/non-existing-path', 'DummyController@start');
});
if ($param1 === 'param1' && $param2 === 'param2') {
$this->result = true;
}
$found = false;
});
try {
SimpleRouter::start();
} catch (\Exception $e) {
$found = ($e instanceof NotFoundHttpException && $e->getCode() == 404);
}
TestRouter::debug('/test-param1-param2', 'get');
$this->assertTrue($found);
}
$this->assertTrue($this->result);
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 testNotFound()
{
$this->setExpectedException('\Pecee\SimpleRouter\Exceptions\NotFoundHttpException');
TestRouter::get('/non-existing-path', 'DummyController@method1');
TestRouter::debug('/test-param1-param2', 'post');
}
public function testPost()
{
SimpleRouter::router()->reset();
SimpleRouter::request()->setUri('/my/test/url');
SimpleRouter::request()->setMethod('post');
public function testGet()
{
TestRouter::get('/my/test/url', 'DummyController@method1');
TestRouter::debug('/my/test/url', 'get');
}
SimpleRouter::post('/my/test/url', 'DummyController@start');
SimpleRouter::start();
}
public function testPost()
{
TestRouter::post('/my/test/url', 'DummyController@method1');
TestRouter::debug('/my/test/url', 'post');
}
public function testPut()
{
SimpleRouter::router()->reset();
SimpleRouter::request()->setUri('/my/test/url');
SimpleRouter::request()->setMethod('put');
public function testPut()
{
TestRouter::put('/my/test/url', 'DummyController@method1');
TestRouter::debug('/my/test/url', 'put');
}
SimpleRouter::put('/my/test/url', 'DummyController@start');
SimpleRouter::start();
}
public function testDelete()
{
TestRouter::delete('/my/test/url', 'DummyController@method1');
TestRouter::debug('/my/test/url', 'delete');
}
public function testDelete()
{
SimpleRouter::router()->reset();
SimpleRouter::request()->setUri('/my/test/url');
SimpleRouter::request()->setMethod('delete');
public function testMethodNotAllowed()
{
TestRouter::get('/my/test/url', 'DummyController@method1');
SimpleRouter::delete('/my/test/url', 'DummyController@start');
SimpleRouter::start();
}
try {
TestRouter::debug('/my/test/url', 'post');
} catch (\Exception $e) {
$this->assertEquals(403, $e->getCode());
}
}
public function testMethodNotAllowed()
{
SimpleRouter::router()->reset();
SimpleRouter::request()->setUri('/my/test/url');
SimpleRouter::request()->setMethod('post');
public function testSimpleParam()
{
TestRouter::get('/test-{param1}', 'DummyController@param');
$response = TestRouter::debugOutput('/test-param1', 'get');
SimpleRouter::get('/my/test/url', 'DummyController@start');
$this->assertEquals('param1', $response);
}
try {
SimpleRouter::start();
} catch (\Exception $e) {
$this->assertEquals(403, $e->getCode());
}
}
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');
public function testSimpleParam()
{
SimpleRouter::router()->reset();
SimpleRouter::request()->setMethod('get');
SimpleRouter::request()->setUri('/test-param1');
$this->assertEquals('it, system', $response);
}
SimpleRouter::get('/test-{param1}', 'DummyController@param');
SimpleRouter::start();
}
public function testDomainAllowedRoute()
{
$this->result = false;
public function testMultiParam()
{
SimpleRouter::router()->reset();
SimpleRouter::request()->setMethod('get');
SimpleRouter::request()->setUri('/test-param1-param2');
TestRouter::group(['domain' => '{subdomain}.world.com'], function () {
TestRouter::get('/test', function ($subdomain = null) {
$this->result = ($subdomain === 'hello');
});
});
SimpleRouter::get('/test-{param1}-{param2}', 'DummyController@param');
SimpleRouter::start();
}
TestRouter::request()->setHost('hello.world.com');
TestRouter::debug('/test', 'get');
public function testPathParamRegex()
{
SimpleRouter::router()->reset();
SimpleRouter::request()->setMethod('get');
SimpleRouter::request()->setUri('/test/path/123123');
$this->assertTrue($this->result);
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');
public function testDomainNotAllowedRoute()
{
$this->result = false;
$this->result = false;
TestRouter::group(['domain' => '{subdomain}.world.com'], function () {
TestRouter::get('/test', function ($subdomain = null) {
$this->result = ($subdomain === 'hello');
});
});
SimpleRouter::group(['domain' => '{subdomain}.world.com'], function () {
SimpleRouter::get('/test', function ($subdomain = null) {
$this->result = ($subdomain === 'hello');
});
});
TestRouter::request()->setHost('other.world.com');
SimpleRouter::start();
$this->assertTrue($this->result);
TestRouter::debug('/test', 'get');
}
$this->assertFalse($this->result);
}
public function testRegEx()
{
TestRouter::get('/my/{path}', 'DummyController@method1')->where(['path' => '[a-zA-Z\-]+']);
TestRouter::debug('/my/custom-path', 'get');
}
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);
}
}
+132 -60
View File
@@ -3,99 +3,171 @@
require_once 'Dummy/DummyMiddleware.php';
require_once 'Dummy/DummyController.php';
require_once 'Dummy/Handler/ExceptionHandler.php';
use Pecee\SimpleRouter\SimpleRouter as SimpleRouter;
require_once 'Helpers/TestRouter.php';
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 testIssue253()
{
TestRouter::get('/', 'DummyController@method1');
TestRouter::get('/page/{id?}', 'DummyController@method1');
TestRouter::get('/test-output', function() {
return 'return value';
});
public function testUrls()
{
SimpleRouter::router()->reset();
SimpleRouter::request()->setMethod('get');
SimpleRouter::request()->setUri('/');
TestRouter::debugNoReset('/page/22', 'get');
$this->assertEquals('/page/{id?}/', TestRouter::router()->getRequest()->getLoadedRoute()->getUrl());
// Match normal route on alias
SimpleRouter::get('/', 'DummyController@silent', ['as' => 'home']);
TestRouter::debugNoReset('/', 'get');
$this->assertEquals('/', TestRouter::router()->getRequest()->getLoadedRoute()->getUrl());
SimpleRouter::get('/about', 'DummyController@about');
$output = TestRouter::debugOutput('/test-output', 'get');
$this->assertEquals('return value', $output);
SimpleRouter::group(['prefix' => '/admin', 'as' => 'admin'], function() {
TestRouter::router()->reset();
}
// Match route with prefix on alias
SimpleRouter::get('/{id?}', 'DummyController@start', ['as' => 'home']);
public function testUnicodeCharacters()
{
// Test spanish characters
TestRouter::get('/cursos/listado/{listado?}/{category?}', '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());
// Match controller with prefix and alias
SimpleRouter::controller('/users', 'DummyController', ['as' => 'users']);
// 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());
// Match controller with prefix and NO alias
SimpleRouter::controller('/pages', 'DummyController');
TestRouter::get('/test/{param}', 'DummyController@method1', ['defaultParameterRegex' => '[\w\p{L}\s-\í]+']);
TestRouter::debugNoReset('/test/Dermatología');
});
$parameters = TestRouter::request()->getLoadedRoute()->getParameters();
SimpleRouter::group(['prefix' => 'api', 'as' => 'api'], function() {
$this->assertEquals('Dermatología', $parameters['param']);
// Match resource controller
SimpleRouter::resource('phones', 'DummyController');
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');
SimpleRouter::controller('gadgets', 'DummyController', ['names' => ['getIphoneInfo' => 'iphone']]);
TestRouter::debugNoReset('/aviso/optional', 'get');
$this->assertEquals('/aviso/{aviso}/', TestRouter::router()->getRequest()->getLoadedRoute()->getUrl());
// Match controller with no prefix and no alias
SimpleRouter::controller('/cats', 'CatsController');
TestRouter::debugNoReset('/pagina/optional', 'get');
$this->assertEquals('/pagina/{pagina}/', TestRouter::router()->getRequest()->getLoadedRoute()->getUrl());
// Pretend to load page
SimpleRouter::start();
TestRouter::debugNoReset('/optional', 'get');
$this->assertEquals('/{pagina?}/', TestRouter::router()->getRequest()->getLoadedRoute()->getUrl());
$this->assertEquals('/gadgets/iphoneinfo/', $this->getUrl('gadgets.iphone'));
TestRouter::debugNoReset('/avisolegal', 'get');
$this->assertNotEquals('/aviso/{aviso}/', TestRouter::router()->getRequest()->getLoadedRoute()->getUrl());
$this->assertEquals('/api/phones/create/', $this->getUrl('api.phones.create'));
TestRouter::debugNoReset('/avisolegal', 'get');
$this->assertEquals('/{pagina?}/', TestRouter::router()->getRequest()->getLoadedRoute()->getUrl());
// Should match /
$this->assertEquals('/', $this->getUrl('home'));
TestRouter::router()->reset();
}
// Should match /about/
$this->assertEquals('/about/', $this->getUrl('DummyController@about'));
public function testSimilarUrls()
{
// Match normal route on alias
TestRouter::resource('/url11', 'DummyController@method1');
TestRouter::resource('/url1', 'DummyController@method1', ['as' => 'match']);
// Should match /admin/
$this->assertEquals('/admin/', $this->getUrl('DummyController@start'));
TestRouter::debugNoReset('/url1', 'get');
// Should match /admin/
$this->assertEquals('/admin/', $this->getUrl('admin.home'));
$this->assertEquals(TestRouter::getUrl('match'), TestRouter::getUrl());
// Should match /admin/2/
$this->assertEquals('/admin/2/', $this->getUrl('admin.home', ['id' => 2]));
TestRouter::router()->reset();
}
// Should match /admin/users/
$this->assertEquals('/admin/users/', $this->getUrl('admin.users'));
public function testUrls()
{
// Match normal route on alias
TestRouter::get('/', 'DummyController@method1', ['as' => 'home']);
// Should match /admin/users/home/
$this->assertEquals('/admin/users/home/', $this->getUrl('admin.users@home'));
TestRouter::get('/about', 'DummyController@about');
// Should match /cats/
$this->assertEquals('/cats/', $this->getUrl('CatsController'));
TestRouter::group(['prefix' => '/admin', 'as' => 'admin'], function () {
// Should match /cats/view/
$this->assertEquals('/cats/view/', $this->getUrl('CatsController', 'view'));
// Match route with prefix on alias
TestRouter::get('/{id?}', 'DummyController@method2', ['as' => 'home']);
// Should match /cats/view/
//$this->assertEquals('/cats/view/', $this->getUrl('CatsController', ['view']));
// Match controller with prefix and alias
TestRouter::controller('/users', 'DummyController', ['as' => 'users']);
// Should match /cats/view/666
$this->assertEquals('/cats/view/666/', $this->getUrl('CatsController@getView', ['666']));
// Match controller with prefix and NO alias
TestRouter::controller('/pages', 'DummyController');
// 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']));
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();
}
}