Compare commits

...

158 Commits

Author SHA1 Message Date
Simon Sessingø da6b5af19f Merge pull request #157 from skipperbent/v2-development
Bugfixes
2016-11-19 09:47:19 +02:00
Simon Sessingø 808d59d3d3 Bugfixes 2016-11-19 08:46:48 +01:00
Simon Sessingø 914ec9d1b7 Merge pull request #155 from skipperbent/v2-development
Small optimisations
2016-11-19 07:02:23 +02:00
Simon Sessingø d1f33d9b01 Small optimisation 2016-11-19 06:01:53 +01:00
Simon Sessingø 5f72755a98 Merge pull request #153 from skipperbent/v2-development
V2 development
2016-11-19 06:25:08 +02:00
Simon Sessingø d4a04920b8 More development 2016-11-19 05:22:51 +01:00
Simon Sessingø 4e12cb8bc3 More changes 2016-11-19 05:06:47 +01:00
Simon Sessingø 8f33cc1a39 Development 2016-11-19 04:41:29 +01:00
Simon Sessingø ed1ac74e7a Development
- Fixed updatae causing middlewares to sometimes load on wrong routes.
- Converted project to PSR/2.
- Updated InputCollection class and added get method for easy access to values.
- Complete refactor of RouterBase.
- Added findRoute method to RouterBase.
- It's now possible to change parameter modifiers and symbol by overwriting properties on RouterBase.
- Added RouterUrlTest unit-test for testing route-urls.
- Added IRestController that can be easily implemented in custom ResourceController-classes.
- It's now possible to use "-" instead of "_" when using getHeader method in Request class.
- Added PHPDocs.
- Fixed "/" route sometimes returning "//" as url.
- Optimisations and bugfixes.
2016-11-19 02:48:19 +01:00
Simon Sessingø 41d15d3acd Merge pull request #151 from skipperbent/v2-development
Re-added missing methods from version 1.
2016-11-17 19:01:17 +02:00
Simon Sessingø a4447313f6 Re-added missing methods from version 1. 2016-11-17 17:55:34 +01:00
Simon Sessingø ded9c8ebe0 Merge pull request #149 from skipperbent/v2-development
Updated documentation
2016-11-17 17:33:49 +02:00
Simon Sessingø 99f869b57d Updated documentation 2016-11-17 16:33:27 +01:00
Simon Sessingø 394f7beb8b Merge pull request #147 from skipperbent/v2-development
V2 development
2016-11-17 17:25:30 +02:00
Simon Sessingø c94523740b Development
- Fixed ExceptionHandlers loading in reverse order.
- Fixed rewrite when modifying `Request` didn't work properly.
2016-11-17 16:24:24 +01:00
Simon Sessingø 305c0ab7c8 Removed clone from RouterBase 2016-11-17 15:17:27 +01:00
Simon Sessingø baab004482 Merge pull request #145 from skipperbent/v2-development
Added default null parameter value to RouterController and RouterResource
2016-11-17 16:04:38 +02:00
Simon Sessingø eb160ff7bb Added default null parameter value to RouterController and RouterResource 2016-11-17 15:03:18 +01:00
Simon Sessingø 00c0cad211 Merge pull request #143 from skipperbent/v2-development
Bugfix: set parameters to null when new url is set
2016-11-17 15:21:24 +02:00
Simon Sessingø 2db0601e20 - Fixed: set parameters to null when new url is set 2016-11-17 14:20:29 +01:00
Simon Sessingø c6e85676da Merge pull request #141 from skipperbent/v2-development
Bugfixes
2016-11-17 06:37:03 +02:00
Simon Sessingø c59ab12e1a Bugfixes 2016-11-17 05:36:44 +01:00
Simon Sessingø 5a74c9d27d Merge pull request #139 from skipperbent/v2-development
Development
2016-11-17 05:03:30 +02:00
Simon Sessingø b298665d33 Changed demo-project to use v2 router 2016-11-17 04:02:53 +01:00
Simon Sessingø 9ba531d559 Removed uncommented code 2016-11-17 04:00:59 +01:00
Simon Sessingø 73ee4521bc Bugfixes
- Array arguments are now longer automaticially merged.
- Added domain-route parameter unit-test.
2016-11-17 03:50:33 +01:00
Simon Sessingø b5f8d9410f Bugfixes 2016-11-17 03:17:11 +01:00
Simon Sessingø b5eef6f3ee Merge pull request #137 from skipperbent/v2-development
Bugfixes
2016-11-16 16:16:22 +02:00
Simon Sessingø 75566dc2ba Bugfixes 2016-11-16 15:15:51 +01:00
Simon Sessingø ad0eceb814 Merge pull request #135 from skipperbent/v2-development
Bugfixes
2016-11-16 15:32:32 +02:00
Simon Sessingø 8478899eb6 Bugfixes 2016-11-16 14:30:19 +01:00
Simon Sessingø 4bec2bc5fb Merge pull request #133 from skipperbent/v2-development
Csrf bugfixes
2016-11-15 09:46:54 +02:00
Simon Sessingø 93562bd758 Csrf bugfixes 2016-11-15 08:46:14 +01:00
Simon Sessingø 4f47463497 Merge pull request #131 from skipperbent/v2-development
V2 development
2016-11-15 08:13:09 +02:00
Simon Sessingø 9c413a3c53 Bugfixes 2016-11-15 06:38:39 +01:00
Simon Sessingø 669d318a12 Documentation updates 2016-11-15 05:57:58 +01:00
Simon Sessingø 9513e38009 Fixed to unit tests 2016-11-15 05:57:04 +01:00
Simon Sessingø a13bcd4768 Updated demo project + documentation 2016-11-15 05:48:08 +01:00
Simon Sessingø 83c73a4240 Callback, faking route and documentation updates 2016-11-15 05:44:24 +01:00
Simon Sessingø bc14790a67 Updated documentation 2016-11-15 03:55:28 +01:00
Simon Sessingø d5e7a13d89 Development + bugfixes 2016-11-15 03:43:26 +01:00
Simon Sessingø e5c86c1822 Merge pull request #129 from skipperbent/v2-development
Bugfixes
2016-11-15 01:12:41 +02:00
Simon Sessingø 6de0700e17 Bugfixes
- Removed debugging constructor.
- Updated documentation.
2016-11-15 00:11:57 +01:00
Simon Sessingø 32c305bd2c Merge pull request #124 from skipperbent/v2-development
V2 development
2016-11-08 18:22:49 +02:00
Simon Sessingø 28ffa30d3e Development
- all() in Input class now returns correct array.
- all() now supports json data.
- Minor bugfixes.
2016-11-08 18:21:21 +02:00
Simon Sessingø 540ebb31ac Updated documentation 2016-11-07 05:48:02 +01:00
Simon Sessingø 48317ded7a Updated documentation 2016-11-07 05:40:32 +01:00
Simon Sessingø 28c3370b67 Fixed group not adding multiple middlewares 2016-11-07 04:55:43 +01:00
Simon Sessingø 7ee42c98a7 Updated documentaiton 2016-11-07 04:45:18 +01:00
Simon Sessingø 8740db9582 Development
- Isolated Http classes from router.
- Removed getInstance from Request object - current router request should now be obtained through SimpleRouter::request().
- Fixed broken test cases.
- Added test for regular expression match.
- Updated documentation to reflect changes.
- Added more helper examples to documentation.
- Added helpers to demo-project.
- Optimisations.
2016-11-07 04:40:28 +01:00
Simon Sessingø 2d57b45c7b Fixed urls not being visible 2016-11-06 09:04:05 +01:00
Simon Sessingø 98cc8504d4 Development
- Group only loads if prefix matches (if any).
2016-11-06 08:13:47 +01:00
Simon Sessingø 035a5b1629 Development
- Added support for cloudflare when using getIp method in Request.
- Fixed undefined variable notice in RouterBase class.
2016-11-05 23:07:14 +01:00
Simon Sessingø 832aff0358 Optimised for cli-usage 2016-10-28 07:40:51 +02:00
Simon Sessingø 43e05ad821 Minor optimisations 2016-10-27 19:15:38 +02:00
Simon Sessingø 2fd32868c2 Optimisations and bugfixes 2016-10-27 17:06:05 +02:00
Simon Sessingø e51b72f0e0 Development
- Changed from http_build_query to custom solution as it doesn't support querystrings with "%" on some php versions.
2016-10-27 16:44:35 +02:00
Simon Sessingø 3b5e2aee9d Added credits 2016-10-20 08:37:39 +02:00
Simon Sessingø 27ba532b2d Updated documentation 2016-10-20 08:36:50 +02:00
Simon Sessingø a8620cbc70 Updates
- Simplified exception-handling (see demo project for examples).
- Optimised sample-project.
- Optimised and added further unit-tests.
- Optimised and bugfixes.
2016-10-20 08:31:21 +02:00
Simon Sessingø 4e054dccf5 Updated documentation 2016-10-04 02:51:37 +02:00
Simon Sessingø e7dfbb159c Updated documentation 2016-10-04 02:50:36 +02:00
Simon Sessingø 8c5a5327d1 Update documentation 2016-10-04 02:47:28 +02:00
Simon Sessingø 3c9a675f25 Optimised middleware load order 2016-09-28 12:29:30 +02:00
Simon Sessingø dfccd99f2f Automatically push middlewares if multiple in nested group. 2016-09-28 12:21:23 +02:00
Simon Sessingø 980a4e9b6a Updated documentation and added demo-project. 2016-09-26 14:37:54 +02:00
Simon Sessingø fab0d0641c Allow for default parameters (including A-Z, a-z, 0-9 and "-") when parsing parameters in RouterResource. 2016-06-14 22:20:05 +02:00
Simon Sessingø 4169716f87 Bugfix
- Fixed some routes with ending slash not matching on when using Ressource.
2016-06-04 18:44:33 +02:00
Simon Sessingø 53e5b5362f Development
- Enhanced regular expression used for matching parameters.
- Added basic unit-tests for parameters.
- Fixed typos in PHP-docs and other minor optimizations.
2016-06-04 18:20:46 +02:00
Simon Sessingø 6780b24e59 Development
- Optimized the way parameters are parsed as a result, simple-router now supports routes like `/{param1}-{param2}.json`.
- Replaced reg-ex for parameter-matching with `\w` which means that default parameter matching on routes now include `_` (underscore) per default.
- Simplified `MiddlewareTest` class.
2016-06-04 15:12:04 +02:00
Simon Sessingø b540c01650 Update README.md 2016-06-03 00:02:45 +02:00
Simon Sessingø eb8832beec Added setValue method to InputItem class. 2016-05-04 13:35:23 +02:00
Simon Sessingø eeafcb7862 Added exist method to Input class. 2016-05-03 07:20:52 +02:00
Simon Sessingø 1a9351c690 - Fixed notifications if included in projects running in command-line where certain variables aren't available. 2016-05-01 00:02:42 +02:00
Simon Sessingø f98e5ac59d - Optimized handleException method in RouterBase. 2016-04-23 10:52:51 +02:00
Simon Sessingø a2dbf4149b - Added route to ExceptionHandler so Middlewares can be loaded. 2016-04-23 10:50:56 +02:00
Simon Sessingø ba736b47a3 - Removed support from middlewares on each load - this should be implemented in custom Router instead.
- Updated documentation to reflect how to implement Middlewares loaded on each route.
2016-04-22 15:37:27 +02:00
Simon Sessingø 6ab1200fd5 - Return $item if it's an array in get method in Input class. 2016-04-22 14:29:21 +02:00
Simon Sessingø 67a00c6800 - Optimized Input class.
- Input class now returns array instead of InputItem instance when object is of type array.
- Optimized key behavior in Input class.
- Optimized arrayToParams method in RouterBase class.
- Added getMergableSettings method to RouterGroup to avoid middleware merge; as they will already be loaded.
2016-04-22 12:55:24 +02:00
Simon Sessingø 1420203149 - Added custom ExceptionHandler example to documentation.
- Fixed reference to request() helper in Input class.
- Changed RouterBase handleException method to support 404-exceptions.
2016-04-21 07:16:29 +02:00
Simon Sessingø f7af53a9af - Optimized Input class to ensure that InputFile items are always returned as object as they contain no value. 2016-04-19 14:48:26 +02:00
Simon Sessingø 6b8351f1b8 - Input class no longer tries to search for parameter in FILE or POST if it's not a postback. 2016-04-19 02:08:14 +02:00
Simon Sessingø eb3ddf2bf7 [OPTIMISATION] Optimized get method in Input class to trim value and return default value if empty. 2016-04-16 21:34:22 +02:00
Simon Sessingø 2afe784f47 [BUGFIX] Fixed middlewaresToLoad logic used before any routes loaded in RouterBase. 2016-04-16 13:08:32 +02:00
Simon Sessingø 94e98ad5f0 Update README.md 2016-04-16 00:01:12 +02:00
Simon Sessingø 8f24256434 Merge pull request #92 from skipperbent/feature-input
Removed old input method from Request class.
2016-04-15 23:20:50 +02:00
Simon Sessingø ec355c90b5 Removed old input method from Request class. 2016-04-15 23:20:16 +02:00
Simon Sessingø a8633b5c51 Merge pull request #90 from skipperbent/feature-input
Feature input
2016-04-15 23:12:40 +02:00
Simon Sessingø 7fdeef74d6 InputFile now inherits from InputItem 2016-04-15 23:10:49 +02:00
Simon Sessingø 17adfb8aa4 [FEATURE] Added Input classes from pecee-framework.
- Updated documentation to reflect new changes.
2016-04-15 23:07:51 +02:00
Simon Sessingø 7a429cec1d [BUGFIX] Fixed get-parameters as array (param[id]=value) causing array-to-string notice. 2016-04-14 23:52:08 +02:00
Simon Sessingø 3da7c4b446 - Fixed getIp in HttpRequest class not picking up local-ip.
- Made setdefaultNamespace method chainable.
- Simplified SimpleRouter class.
2016-04-11 22:37:15 +02:00
Simon Sessingø f3ac9dc47c [BUGFIX] Fixed notice 2016-04-09 15:38:45 +02:00
Simon Sessingø 22563671c5 [TASK] Moved group-middleware rendering to routeRequest to ensure all route-urls has been initialised.
- Optimisations.
2016-04-09 15:26:18 +02:00
Simon Sessingø 491e920cfc [BUGFIX] Fixed only match group route if prefix is set 2016-04-09 10:01:02 +02:00
Simon Sessingø 6cef099110 [TASK] Updated documentation with list of sites that uses project in production 2016-04-09 09:49:59 +02:00
Simon Sessingø 5f95290e4b [OPTIMISATION] Optimised exception-message thrown when not using IExceptionHandler interface. 2016-04-09 09:45:39 +02:00
Simon Sessingø 7234415e24 [TASK] Made abstract ExceptionHandler class an interface.
- Updated documentation to reflect new changes.
2016-04-09 09:42:57 +02:00
Simon Sessingø 257875c6f9 [TASK] Load group middleware if prefix matches
- Middlewares from groups are now loaded if prefix matches.
- Optimisations.
2016-04-09 09:25:41 +02:00
Simon Sessingø 9b743e6e57 [FEATURE] Added requirements (issue: #80) 2016-04-09 08:05:18 +02:00
Simon Sessingø 457dbc5710 [BUGFIX] Fixed issue #83
- Added more tests.
2016-04-09 07:51:03 +02:00
Simon Sessingø fc4fd0edf1 [FEATURE] Added ExceptionHandling functionality + tests.
- Somehow the ExceptionHandling functionality never really get implemented and/or got lost in translation, it's back whup!
- Added some basic tests for Middleware + routing.
2016-04-09 07:05:49 +02:00
Simon Sessingø 2f2c3ca3ca [OPTIMISATIONS] Optimised code and removed unused references 2016-04-09 05:58:18 +02:00
Simon Sessingø b34738a51a [OPTIMISATION] Optimised CSRF-token management. 2016-04-09 05:51:27 +02:00
Simon Sessingø 975c27659c [BUGFIX] Bugfixes and optimisations
- Fixed issue causing nested groups not merging namespace correctly.
- Fixed issue causing longer urls (for example: /route) to have higher priority than (/route/match) in some cases.
2016-04-07 23:16:50 +02:00
Simon Sessingø 27371dfa11 [FEATURE] Added support for multiple aliases 2016-04-07 19:33:04 +02:00
Simon Sessingø 6e102711a8 [BUGFIX] Fixed nested groups not merging settings to routes 2016-04-07 01:38:03 +02:00
Simon Sessingø 4d58fbf7b5 [TASK] Added rewrite_uri parameter to Request class 2016-03-19 19:14:07 +01:00
Simon Sessingø 35bb58818e [BUGFIX] Readded rendering of groups 2016-03-19 19:00:23 +01:00
Simon Sessingø 438d3c258b [TASK] Readded merging 2016-03-19 17:11:54 +01:00
Simon Sessingø a58ec1544d Merge branch 'development' of https://github.com/skipperbent/simple-php-router into development 2016-03-19 16:27:54 +01:00
Simon Sessingø 1bafbab56b [TASK] Moved RouterException. Readded lost stuff from Request. 2016-03-19 16:27:25 +01:00
Simon Sessingø 6395801a06 [OPTIMISATIONS] Documentation 2016-03-19 16:19:04 +01:00
Simon Sessingø 060479f1fe Update README.md 2016-03-19 16:18:33 +01:00
Simon Sessingø b29f2ce37d [FEATURE] Added custom boot managers 2016-03-19 16:14:36 +01:00
Simon Sessingø 5483ffe458 [TASK] Readded group match inheritence .
- Settings on routes now have higher priority when merging settings from
  group.
2016-03-18 17:43:57 +01:00
Simon Sessingø 6d8e95fcaa [TASK] Match on group are no longer merged to child routes. 2016-03-18 15:21:45 +01:00
Simon Sessingø db78135d19 [BUGFIX] Only render group if prefix matches. 2016-03-16 19:57:54 +01:00
Simon Sessingø c4fcf750d4 Added support for x-forwarded-proto http header 2016-03-14 01:09:35 +01:00
Simon Sessingø a92b6008fa [TASK] Added http code to redirect method. 2016-03-14 01:00:19 +01:00
Simon Sessingø 115c8e510a [BUGFIX] Added urldecode to RouterRessource, RouterRoute and
RouterController class to ensure that urls with special letters can be
picked up by a custom regular expression (read documentation: match).
2016-03-01 22:50:26 +01:00
Simon Sessingø 35ee79d02c [TASK] Fixed regex causing optional parameters to sometimes catch required
parameters value.
2016-01-23 16:10:44 +01:00
Simon Sessingø 6306b604e8 Merge branch 'development' of https://github.com/skipperbent/simple-php-router into development 2016-01-17 04:56:56 +01:00
Simon Sessingø 4dd0739df9 [BUGFIX] Fixed minor errors with optional parameter when using getRoute(). 2016-01-17 04:56:29 +01:00
Simon Sessingø 0bd234d996 Merge pull request #65 from skipperbent/master
Latest development
2016-01-15 11:55:47 +01:00
Simon Sessingø 27d24758b1 [OPTIMISATION] Ensured action parameter was set before using it. 2016-01-15 11:55:01 +01:00
Simon Sessingø 4d08f08c68 Merge pull request #64 from skipperbent/development
Development
2016-01-15 11:09:00 +01:00
Simon Sessingø aeacda1812 [FEATURE] Added option to change route and get information about current
route through the Request object.

- Updated documentation to relfect new changes.
2016-01-15 11:06:13 +01:00
Simon Sessingø 765204f552 [FEATURE] Moved loadedRoute to request so it can be easily overwritten
from middleware.
2016-01-15 10:34:59 +01:00
Simon Sessingø 4267cb8751 Merge pull request #63 from skipperbent/development
[OPTIMISATION] Parameters matching optimisations.
2016-01-15 10:22:14 +01:00
Simon Sessingø 714edf7902 [OPTIMISATION] Parameters matching optimisations. 2016-01-15 10:20:50 +01:00
Simon Sessingø c3b12ba053 Merge pull request #62 from skipperbent/development
Development
2016-01-15 10:07:51 +01:00
Simon Sessingø b096742d6b [BUGFIX] Enchanched regular expression for optinal parameters to
completely ignore path seperators (/).
2016-01-15 10:06:13 +01:00
Simon Sessingø 3298970798 Merge pull request #61 from skipperbent/development
[BUGFIX] Fixed some ressources not working after latest update.
2016-01-15 09:39:28 +01:00
Simon Sessingø bb6f56ef8c [BUGFIX] Fixed some ressources not working after latest update.
- Optimisations + cleanup.
2016-01-15 09:37:23 +01:00
Simon Sessingø 6c675124fa Merge pull request #60 from skipperbent/development
[BUGFIX] Minor bugfixes and optimisations
2016-01-14 16:41:37 +01:00
Simon Sessingø 14a030294e [BUGFIX] Minor bugfixes and optimisations
- Fixed getRoute sometimes not passing current loaded route.
- Fixed optional parameters in some occasions not working properly.
2016-01-14 16:37:36 +01:00
Simon Sessingø 8bc8124366 Merge pull request #59 from skipperbent/development
[TASK] Simplified regex for matching parameters.
2016-01-05 05:50:35 +01:00
Simon Sessingø 1332ef7139 [TASK] Optimised regex for matching parameters. 2016-01-05 04:52:12 +01:00
Simon Sessingø 12bbc98a82 Merge pull request #58 from skipperbent/development
[BUGFIX] Custom parameters regex is now working again.
2015-12-24 00:48:27 +01:00
Simon Sessingø 69b0f4320e [BUGFIX] Fixed show not always called if using nested ressources. 2015-12-24 00:39:21 +01:00
Simon Sessingø 4c60055c7e [BUGFIX] Custom parameters regex is now working again.
- Fixed custom parameters regex not working.
- Renamed setting "parameterRegex" to "match" to match the Laravel
  documentation.
2015-12-23 20:25:26 +01:00
Simon Sessingø 89aeeeb593 Merge pull request #57 from skipperbent/development
[TASK]
2015-12-19 20:25:36 +01:00
Simon Sessingø 849749969d [OPTIMISATION] Made getAllHeaders protected. 2015-12-19 20:20:42 +01:00
Simon Sessingø c37a7159d2 [OPTIMISATION] No reason to str_replace twice. 2015-12-19 20:18:35 +01:00
Simon Sessingø df2545dd37 [TASK]
- Added support for setups where getallheaders function is not availible.
- Made matchDomain method in RouterGroup always return boolean.
2015-12-19 20:15:10 +01:00
Simon Sessingø bd0a6af41f Merge pull request #56 from skipperbent/development
[BUGFIX] Wrong validation on domain
2015-12-19 18:25:56 +01:00
Simon Sessingø 3510c4c5a4 [BUGFIX] Wrong validation on domain 2015-12-19 18:25:18 +01:00
Simon Sessingø 7ff88bc658 Merge pull request #55 from skipperbent/development
[BUGFIX] Bugfixes.
2015-12-19 18:09:08 +01:00
Simon Sessingø ce27196083 [BUGFIX] Bugfixes.
- Fixed not validating domain on
- Other small changes.
2015-12-19 18:07:18 +01:00
Simon Sessingø 9304b9f866 Merge pull request #54 from skipperbent/development
Development
2015-12-19 16:28:17 +01:00
Simon Sessingø ed886cb7b3 Merge pull request #53 from skipperbent/feature-resource
[FEATURE] ResourceControllers now support nested ressources
2015-12-19 16:28:06 +01:00
Simon Sessingø cbaa0bcaac [FEATURE] getRoute now support domains.
- Optimisations + bugfixes.
2015-12-19 09:21:12 +01:00
Simon Sessingø 98b1759967 [TASK] Updated documentation. 2015-12-19 07:19:49 +01:00
Simon Sessingø b9be7695a7 [TASK] Fixed parameter matching regex when using multiple optional parameters. 2015-12-19 07:15:32 +01:00
Simon Sessingø 97e2edd207 [FEATURE] ResourceControllers now support nested ressources 2015-12-17 14:39:47 +01:00
Simon Sessingø 87fbbcbba2 Merge pull request #52 from skipperbent/development
[TASK] Csrf-token fixes + readded BaseCsrfVerifier.
2015-12-14 13:40:19 +01:00
Simon Sessingø 5a501db767 [TASK] Csrf-token fixes + readded BaseCsrfVerifier.
- Readded BaseCsrfVerifier middleware.
- Csrf-token expire time is now updated on each page refresh.
- CSRF-token update now happens after the route has been loaded, to ensure
  no faulty "Invalid csrf-token" exceptions.
2015-12-14 13:36:38 +01:00
49 changed files with 3734 additions and 1581 deletions
+3 -1
View File
@@ -1,2 +1,4 @@
.idea
composer.lock
composer.lock
vendor/
demo-project/vendor
+310 -78
View File
@@ -1,19 +1,16 @@
# Simple PHP router
Simple, fast and yet powerful PHP router that is easy to get integrated and in any project. Heavily inspired by the Laravel router.
Simple, fast and yet powerful PHP router that is easy to get integrated and in any project. Heavily inspired by the way Laravel handles routing, with both simplicity and expandability in mind.
## Installation
Add the latest version pf Simple PHP Router to your ```composer.json```
Add the latest version of Simple PHP Router running this command.
```json
{
"require": {
"pecee/simple-php-router": "1.*"
},
"require-dev": {
"pecee/simple-php-router": "1.*"
}
}
```
composer require pecee/simple-router
```
## Requirements
- PHP 5.4 or greater
## Notes
@@ -21,7 +18,7 @@ The goal of this project is to create a router that is 100% compatible with the
### Features
- Basic routing (get, post, put, delete) with support for custom multiple verbs.
- Basic routing (`GET`, `POST`, `PUT`, `DELETE`) with support for custom multiple verbs.
- Regular Expression Constraints for parameters.
- Named routes.
- Generating url to routes.
@@ -32,10 +29,16 @@ The goal of this project is to create a router that is 100% compatible with the
- CSRF protection.
- Optional parameters
- Sub-domain routing
- Custom boot managers to rewrite urls to "nicer" ones.
- Input manager; easily manage `GET`, `POST` and `FILE` values.
### Features currently "in-the-works"
## Installation and demo
- Global Constraints
We've included a simple demo project for the router which can be found in the `demo-project` folder.
Please refer to the demo-project documentation for further reading on how to setup and install simple-php-router:
[Link to demo documentation](demo-project/README.md)
## Initialising the router
@@ -44,15 +47,20 @@ In your ```index.php``` require your ```routes.php``` and call the ```routeReque
This is an example of a basic ```index.php``` file:
```php
use \Pecee\SimpleRouter;
use \Pecee\SimpleRouter\SimpleRouter;
require_once 'routes.php'; // change this to whatever makes sense in your project
// Load external routes file
require_once 'routes.php';
// The apps default namespace (so we don't have to specify it each time we use MyController@home)
$defaultControllerNamespace = 'MyWebsite\\Controller';
/*
* The default namespace for route-callbacks, so we don't have to specify it each time.
* Can be overwritten by using the namespace config option.
*/
SimpleRouter::setDefaultNamespace('MyWebsite\Controller');
// Do the routing
SimpleRouter::init($defaultControllerNamespace);
// Start the routing
SimpleRouter::start();
```
## Adding routes
@@ -61,6 +69,10 @@ This router is heavily inspired by the Laravel 5.* router, so anything you find
### Basic example
- ExceptionsHandlers must implement the `IExceptionHandler` interface.
- Middlewares must implement the `IMiddleware` interface.
- Resource controllers can inherit the `IRestController` interface, but is not required.
```php
use Pecee\SimpleRouter\SimpleRouter;
@@ -72,12 +84,17 @@ use Pecee\SimpleRouter\SimpleRouter;
* the request, for instance if a user is not authenticated.
*/
SimpleRouter::group(['prefix' => 'v1', 'middleware' => '\MyWebsite\Middleware\SomeMiddlewareClass'], function() {
// Add CSRF support (if needed)
SimpleRouter::csrfVerifier(new \Pecee\Http\Middleware\BaseCsrfVerifier());
SimpleRouter::get('/page/404', 'ControllerPage@notFound', ['as' => 'page.notfound']);
SimpleRouter::group(['prefix' => '/v1', 'middleware' => '\MyWebsite\Middleware\SomeMiddlewareClass'], function() {
SimpleRouter::group(['prefix' => '/services', 'exceptionHandler' => '\MyProject\Handler\CustomExceptionHandler'], function() {
SimpleRouter::get('/answers/{id}', 'ControllerAnswers@show')->where(['id' => '[0-9]+');
// Optional parameter
SimpleRouter::get('/answers/{id?}', 'ControllerAnswers@show');
@@ -87,7 +104,7 @@ SimpleRouter::group(['prefix' => 'v1', 'middleware' => '\MyWebsite\Middleware\So
*/
SimpleRouter::all('/ajax', 'ControllerAjax@process')->match('.*?\\/ajax\\/([A-Za-z0-9\\/]+)');
// Restful resource
// Restful resource (see IRestController interface for available methods)
SimpleRouter::resource('/rest', 'ControllerRessource');
// Load the entire controller (where url matches method names - getIndex(), postIndex() etc)
@@ -102,6 +119,43 @@ SimpleRouter::group(['prefix' => 'v1', 'middleware' => '\MyWebsite\Middleware\So
});
```
#### ExceptionHandler example
This is a basic example of an ExceptionHandler implementation:
```php
namespace Demo\Handlers;
use Pecee\Http\Request;
use Pecee\SimpleRouter\RouterEntry;
class CustomExceptionHandler implements IExceptionHandler {
public function handleError( Request $request, RouterEntry $router = null, \Exception $error) {
// If the error-code is 404; show another route which contains the page-not-found
if($error->getCode() === 404) {
// Throw your custom 404-page view
// - or -
// load another route with our 404 page
// - or -
// you can return the $request object to ignore the error and continue on rendering the route.
return $request->setUri(url('page.notfound'));
}
// Output error as json if on api path.
if(stripos($request->getUri(), '/api') !== false) {
response()->json([ 'error' => $error->getMessage() ]);
}
// Otherwise default exception will be thrown by the router.
}
}
```
### Sub-domain routing
Route groups may also be used to route wildcard sub-domains. Sub-domains may be assigned route parameters just like route URIs, allowing you to capture a portion of the sub-domain for usage in your route or controller. The sub-domain may be specified using the ```domain``` key on the group attribute array:
@@ -144,77 +198,39 @@ This is a simple example of an integration into a framework.
The framework has it's own ```Router``` class which inherits from the ```SimpleRouter``` class. This allows the framework to add custom functionality.
```php
namespace MyProject;
namespace Demo;
use Pecee\Handler\ExceptionHandler;
use Pecee\SimpleRouter\RouterBase;
use Pecee\SimpleRouter\SimpleRouter;
class Router extends SimpleRouter {
protected static $defaultExceptionHandler;
public static function start() {
public static function start($defaultNamespace = null) {
// change this to whatever makes sense in your project
require_once 'routes.php';
// change default namespace for all routes
parent::setDefaultNamespace('\Demo');
// Load routes.php
$file = $_ENV['base_path'] . DIRECTORY_SEPARATOR . 'lib' . DIRECTORY_SEPARATOR . 'routes.php';
if(file_exists($file)) {
require_once $file;
}
// Do initial stuff
parent::start();
// Set default namespace
$defaultNamespace = '\\'.$_ENV['app_name'] . '\\Controller';
// Handle exceptions
try {
parent::start($defaultNamespace);
} catch(\Exception $e) {
$route = RouterBase::getInstance()->getLoadedRoute();
$exceptionHandler = null;
// Load and use exception-handler defined on group
if($route && $route->getGroup()) {
$exceptionHandler = $route->getGroup()->getExceptionHandler();
if($exceptionHandler !== null) {
$class = new $exceptionHandler();
$class->handleError(RouterBase::getInstance()->getRequest(), $route, $e);
}
}
// Otherwise use the fallback default exceptions handler
if(self::$defaultExceptionHandler !== null) {
$class = new self::$defaultExceptionHandler();
$class->handleError(RouterBase::getInstance()->getRequest(), $route, $e);
}
throw $e;
}
}
public static function setDefaultExceptionHandler($handler) {
self::$defaultExceptionHandler = $handler;
}
}
```
This is a basic example of a helper function for generating urls.
## Helper functions
To simplify to use of simple-router functionality, we recommend you add these helper functions to your project.
```php
use Pecee\SimpleRouter\RouterBase;
use Pecee\SimpleRouter\SimpleRouter;
function url($controller, $parameters = null, $getParams = null) {
RouterBase::getInstance()->getRoute($controller, $parameters, $getParams);
SimpleRouter::getRoute($controller, $parameters, $getParams);
}
```
This is a basic example for getting the current csrf token
```php
/**
* Get current csrf-token
* @return null|string
@@ -223,6 +239,30 @@ function csrf_token() {
$token = new \Pecee\CsrfToken();
return $token->getToken();
}
/**
* Get request object
* @return \Pecee\Http\Request
*/
function request() {
return SimpleRouter::request();
}
/**
* Get response object
* @return \Pecee\Http\Response
*/
function response() {
return SimpleRouter::response();
}
/**
* Get input class
* @return \Pecee\Http\Input\Input
*/
function input() {
return SimpleRouter::request()->getInput();
}
```
## Getting urls
@@ -258,7 +298,10 @@ use Pecee\Http\Middleware\BaseCsrfVerifier;
class CsrfVerifier extends BaseCsrfVerifier {
protected $except = ['/companies/*', '/user/save'];
protected $except = [
'/companies/*',
'/api'
];
}
```
@@ -269,6 +312,192 @@ Register the new class in your ```routes.php```, custom ```Router``` class or wh
SimpleRouter::csrfVerifier(new \Demo\Middleware\CsrfVerifier());
```
## Using router bootmanager to make custom rewrite rules
Sometimes it can be necessary to keep urls stored in the database, file or similar. In this example, we want the url ```/my-cat-is-beatiful``` to load the route ```/article/view/1``` which the router knows, because it's defined in the ```routes.php``` file.
To interfere with the router, we create a class that inherits from ```RouterBootManager```. This class will be loaded before any other rules in ```routes.php``` and allow us to "change" the current route, if any of our criteria are fulfilled (like coming from the url ```/my-cat-is-beatiful```).
```php
use Pecee\Http\Request;
use Pecee\SimpleRouter\RouterBootManager;
class CustomRouterRules extends RouterBootManager{
public function boot(Request $request) {
$rewriteRules = [
'/my-cat-is-beatiful' => '/article/view/1',
'/horses-are-great' => '/article/view/2'
];
foreach($rewriteRules as $url => $rule) {
// If the current uri matches the url, we use our custom route
if($request->getUri() === $url) {
$request->setUri($rule);
}
}
return $request;
}
}
```
The above should be pretty self-explanatory and can easily be changed to loop through urls store in the database, file or cache.
What happens is that if the current route matches the route defined in the index of our ```$rewriteRules``` array, we set the route to the array value instead.
By doing this the route will now load the url ```/article/view/1``` instead of ```/my-cat-is-beatiful```.
The last thing we need to do, is to add our custom boot-manager to the ```routes.php``` file. You can create as many bootmanagers as you like and easily add them in your ```routes.php``` file.
## Easily overwrite route about to be loaded
Sometimes it can be useful to manipulate the route about to be loaded.
simple-php-router allows you to easily change the route about to be executed.
All information about the current route is stored in the ```\Pecee\SimpleRouter\RouterBase``` instance's `loadedRoute` property.
For easy access you can use the shortcut method `\Pecee\SimpleRouter\SimpleRouter::router()`.
```php
use Pecee\SimpleRouter;
$route = SimpleRouter::router()->getLoadedRoute();
$route->setCallback('Example\MyCustomClass@hello');
// -- or you can rewrite by doing --
$route->setClass('Example\MyCustomClass');
$route->setMethod('hello');
```
### Examples
It's only possible to change the route BEFORE the route has initially been loaded. If you want to redirect to another route, we highly recommend that you
modify the `RouterEntry` object from a `Middleware` or `ExceptionHandler`, like the examples below.
#### Rewriting to new route
The example below will cause the router to re-route the request with another url. We are using the `url()` helper function to get the uri to another route added in the `routes.php` file.
**NOTE: Use this method if you want to fully load another route using it's settings (request method etc).**
```php
namespace demo\Middlewares;
use Pecee\Http\Middleware\IMiddleware;
use Pecee\Http\Request;
use Pecee\SimpleRouter\RouterEntry;
class CustomMiddleware implements Middleware {
public function handle(Request $request, RouterEntry &$route) {
$request->setUri(url('home'));
}
}
```
#### Changing callback
You can also change the callback by modifying the `$route` parameter. This is perfect if you just want to display a view quickly - or change the callback depending
on some criteria's for the request.
The callback below will fire immediately after the `Middleware` or `ExceptionHandler` has been loaded, as they are loaded before the route is rendered.
If you wish to change the callback from outside, please have this in mind.
**NOTE: Use this method if you want to load another controller. No additional middlewares or rules will be loaded.**
```php
namespace demo\Middlewares;
use Pecee\Http\Middleware\IMiddleware;
use Pecee\Http\Request;
use Pecee\SimpleRouter\RouterEntry;
class CustomMiddleware implements Middleware {
public function handle(Request $request, RouterEntry &$route) {
$route->callback('DefaultController@home');
}
}
```
## Using the Input class to manage parameters
We've added the `Input` class to easy access parameters from your Controller-classes.
**Return single parameter value (matches both GET, POST, FILE):**
```php
$value = input()->get('name');
```
**Return parameter object (matches both GET, POST, FILE):**
```php
$object = input()->getObject('name');
```
**Return specific GET parameter (where name is the name of your parameter):**
```php
$object = input()->get->name;
$object = input()->post->name;
$object = input()->file->name;
// -- or --
$object = input()->get->get($key, $defaultValue);
$object = input()->post->get($key, $defaultValue);
$object = input()->file->get($key, $defaultValue);
```
**Return all parameters:**
```php
// Get all
$values = input()->all();
// Only match certain keys
$values = input()->all([
'company_name',
'user_id'
]);
```
All object inherits from `InputItem` class and will always contain these methods:
- `getValue()` - returns the value of the input.
- `getIndex()` - returns the index/key of the input.
- `getName()` - returns a human friendly name for the input (company_name will be Company Name etc).
`InputFile` has the same methods as above along with some other file-specific methods like:
- `getTmpName()` - get file temporary name.
- `getSize()` - get file size.
- `move($destination)` - move file to destination.
- `getContents()` - get file content.
- `getType()` - get mime-type for file.
- `getError()` - get file upload error.
Below example requires you to have the helper functions added. Please refer to the helper functions section in the documentation.
```php
// Get parameter site_id or default-value 2
$siteId = input()->get('site_id', 2);
```
## Sites
This is some sites that uses the simple-router project in production.
- [holla.dk](http://www.holla.dk)
- [ninjaimg.com](http://ninjaimg.com)
- [bookandbegin.com](https://bookandbegin.com)
- [dscuz.com](https://www.dscuz.com)
## Documentation
While I work on a better documentation, please refer to the Laravel 5 routing documentation here:
@@ -277,9 +506,12 @@ http://laravel.com/docs/5.1/routing
## Easily extendable
The router can be easily extended to customize your needs.
## Ideas and issues
If you want a great new feature or experience any issues what-so-ever, please feel free to leave an issue and i'll look into it whenever possible.
## The MIT License (MIT)
Copyright (c) 2015 Simon Sessingø / simple-php-router
Copyright (c) 2016 Simon Sessingø / simple-php-router
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+1 -1
View File
@@ -13,7 +13,7 @@
}
],
"require": {
"php": ">=5.4.0"
},
"require-dev": {
"phpunit/phpunit": "4.7.7"
+100
View File
@@ -0,0 +1,100 @@
# Simple PHP router demo project
This project is here to give you a basic understanding of how to setup and using simple-php-router.
Please note that this demo-project only covers how to integrate the `simple-php-router` in a project without a framework. If you are using some sort of PHP framework in your project the implementation might vary.
**What we won't cover:**
- How to setup a solution that fits your need. This is a basic demo to help you get started.
- Understanding of MVC; including Controllers, Middlewares or ExceptionHandlers.
- How to integrate into third party frameworks.
**What we cover:**
- How to get up and running fast - from scratch.
- How to get ExceptionHandlers, Middlewares and Controllers working.
- How to setup your webservers.
## Installation
- Navigate to the `demo-project` folder in terminal and run `composer update` to install the latest version.
- Point your webserver to `demo-project/public`.
### Setting up Nginx
If you are using Nginx please make sure that url-rewriting is enabled.
You can easily enable url-rewriting by adding the following configuration for the Nginx configuration-file for the demo-project.
```
location / {
try_files $uri $uri/ /index.php?$query_string;
}
```
### Setting up Apache
Nothing special is required for Apache to work. We've include the `.htaccess` file in the `public` folder. If rewriting is not working for you, please check that the `mod_rewrite` module (htaccess support) is enabled in the Apache configuration.
## Folder structure
| Folder | Description |
| ------------- |-------------|
| app |Contains projects-specific PHP classes|
| public |Public folder which are accessible through the web.|
## Notes
The demo project has it's own `Router` class implementation which extends the `SimpleRouter` class with further functionality.
This class can be useful adding additional functionality that are required before and after routing occurs or any extra functionality belonging to the router itself.
In this project we also use our custom router-class to autoload the `routes.php` file from our custom location (`app/routes.php`).
Please check the `routes.php` file in `demo-project/app` for all the urls/rules available in the project.
### CSRF-verifier
For the purpose of this demo, we've added a custom CSRF-verifier middleware called `CsrfVerifier` and disabled CSRF checks for all calls to `/api/*`. This will ensure that CSRF form-checks are not applied when calling our demo api url.
### Exception handlers
The included `CustomExceptionHandler` class returns a very basic json response for errors received on calls to `/api/*` or otherwise just a simple formatted error response.
### Middlewares
`ApiVerification` class is added to all calls to `/api/*`. This simple class just adds some data to the `Request` object, which is returned in one of the methods in the `ApiController` class. We've added this class to demonstrate that you can use middlewares to ensure that the user has the correct authentication - before router loads the controller itself.
### Urls
Please see `routes.php` for all routes and rules.
| URL |
| ------------- |
| / |
| /api/demo |
| /companies |
| /companies/[id] |
| /contact |
## The MIT License (MIT)
Copyright (c) 2016 Simon Sessingø / simple-php-router
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,16 @@
<?php
namespace Demo\Controllers;
class ApiController
{
public function index()
{
// The variable authenticated is set to true in the ApiVerification middleware class.
header('content-type: application/json');
echo json_encode([
'authenticated' => request()->authenticated
]);
}
}
@@ -0,0 +1,27 @@
<?php
namespace Demo\Controllers;
class DefaultController
{
public function index()
{
// implement
echo sprintf('DefaultController -> index (?fun=%s)', input()->get('fun'));
}
public function contact()
{
echo 'DefaultController -> contact';
}
public function companies($id = null)
{
echo 'DefaultController -> companies -> id: ' . $id;
}
public function notFound()
{
echo 'Page not found';
}
}
@@ -0,0 +1,34 @@
<?php
namespace Demo\Handlers;
use Pecee\Handler\IExceptionHandler;
use Pecee\Http\Request;
use Pecee\SimpleRouter\RouterEntry;
class CustomExceptionHandler implements IExceptionHandler
{
public function handleError(Request $request, RouterEntry &$route = null, \Exception $error)
{
// Return json errors if we encounter an error on /api.
if (stripos($request->getUri(), '/api') !== false) {
header('content-type: application/json');
echo json_encode([
'error' => $error->getMessage(),
'code' => $error->getCode()
]);
die();
}
// else we just throw the error
if ($error->getCode() == 404) {
// Return 404 path
$request->setUri('/404');
return $request;
}
throw $error;
}
}
@@ -0,0 +1,18 @@
<?php
namespace Demo\Middlewares;
use Pecee\Http\Middleware\IMiddleware;
use Pecee\Http\Request;
use Pecee\SimpleRouter\RouterEntry;
class ApiVerification implements IMiddleware
{
public function handle(Request $request, RouterEntry &$route)
{
// Do authentication
$request->authenticated = true;
return $request;
}
}
@@ -0,0 +1,13 @@
<?php
namespace Demo\Middlewares;
use Pecee\Http\Middleware\BaseCsrfVerifier;
class CsrfVerifier extends BaseCsrfVerifier
{
/**
* CSRF validation will be ignored on the following urls.
*/
protected $except = ['/api/*'];
}
+26
View File
@@ -0,0 +1,26 @@
<?php
/**
* Custom router which handles default middlewares, default exceptions and things
* that should be happen before and after the router is initialised.
*/
namespace Demo;
use Pecee\SimpleRouter\SimpleRouter;
class Router extends SimpleRouter
{
public static function start($defaultNamespace = null)
{
// Load our helpers
require_once 'helpers.php';
// Load our custom routes
require_once 'routes.php';
parent::setDefaultNamespace('\Demo');
// Do initial stuff
parent::start();
}
}
+44
View File
@@ -0,0 +1,44 @@
<?php
use Pecee\SimpleRouter\SimpleRouter;
function url($controller, $parameters = null, $getParams = null)
{
SimpleRouter::getRoute($controller, $parameters, $getParams);
}
/**
* Get current csrf-token
* @return null|string
*/
function csrf_token()
{
$token = new \Pecee\CsrfToken();
return $token->getToken();
}
/**
* Get request object
* @return \Pecee\Http\Request
*/
function request()
{
return SimpleRouter::request();
}
/**
* Get response object
* @return \Pecee\Http\Response
*/
function response()
{
return SimpleRouter::response();
}
/**
* Get input class
* @return \Pecee\Http\Input\Input
*/
function input()
{
return SimpleRouter::request()->getInput();
}
+23
View File
@@ -0,0 +1,23 @@
<?php
/**
* This file contains all the routes for the project
*/
use Demo\Router;
Router::csrfVerifier(new \Demo\Middlewares\CsrfVerifier());
Router::group(['exceptionHandler' => 'Demo\Handlers\CustomExceptionHandler'], function () {
Router::get('/', 'DefaultController@index')->setAlias('home');
Router::get('/contact', 'DefaultController@contact')->setAlias('contact');
Router::get('/404', 'DefaultController@notFound')->setAlias('404');
Router::basic('/companies', 'DefaultController@companies')->setAlias('companies');
Router::basic('/companies/{id}', 'DefaultController@companies')->setAlias('companies');
// Api
Router::group(['prefix' => '/api', 'middleware' => 'Demo\Middlewares\ApiVerification'], function () {
Router::resource('/demo', 'ApiController');
});
});
+26
View File
@@ -0,0 +1,26 @@
{
"name": "pecee/simple-router-demo",
"description": "Simple router demo project",
"keywords": [
"simple-router",
"php",
"php-simple-router"
],
"license": "MIT",
"type": "project",
"require": {
"php": ">=5.4.0",
"pecee/simple-router": "2.*"
},
"require-dev": {
},
"config": {
"preferred-install": "dist"
},
"autoload": {
"psr-4": {
"Demo\\": "app/"
}
}
}
+5
View File
@@ -0,0 +1,5 @@
RewriteEngine on
RewriteCond %{SCRIPT_FILENAME} !-f
RewriteCond %{SCRIPT_FILENAME} !-d
RewriteCond %{SCRIPT_FILENAME} !-l
RewriteRule ^(.*)$ index.php/$1
+7
View File
@@ -0,0 +1,7 @@
<?php
// load composer dependencies
require '../vendor/autoload.php';
// Start the routing
\Demo\Router::start();
+46
View File
@@ -0,0 +1,46 @@
<?php
namespace Pecee\Controller;
interface IRestController {
/**
* @return void
*/
function index();
/**
* @param mixed $id
* @return void
*/
function show($id);
/**
* @return void
*/
function store();
/**
* @return void
*/
function create();
/**
* View
* @param mixed $id
* @return void
*/
function edit($id);
/**
* @param mixed $id
* @return void
*/
function update($id);
/**
* @param mixed $id
* @return void
*/
function destroy($id);
}
+57 -49
View File
@@ -1,60 +1,68 @@
<?php
namespace Pecee;
class CsrfToken {
class CsrfToken
{
const CSRF_KEY = 'XSRF-TOKEN';
const CSRF_KEY = 'XSRF-TOKEN';
protected $token;
protected $token;
/**
* Generate random identifier for CSRF token
*
* @return string
*/
public static function generateToken()
{
if (function_exists('mcrypt_create_iv')) {
return bin2hex(mcrypt_create_iv(32, MCRYPT_DEV_URANDOM));
}
return bin2hex(openssl_random_pseudo_bytes(32));
}
public function __construct() {
if($this->getToken() === null) {
$this->setToken($this->generateToken());
}
}
/**
* 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;
}
/**
* Generate random identifier for CSRF token
* @return string
*/
public static function generateToken() {
if (function_exists('mcrypt_create_iv')) {
return bin2hex(mcrypt_create_iv(32, MCRYPT_DEV_URANDOM));
}
return bin2hex(openssl_random_pseudo_bytes(32));
}
/**
* Set csrf token cookie
*
* @param $token
*/
public function setToken($token)
{
setcookie(static::CSRF_KEY, $token, time() + 60 * 120, '/');
}
/**
* 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;
}
/**
* Get csrf token
* @return string|null
*/
public function getToken()
{
if ($this->hasToken()) {
return $_COOKIE[static::CSRF_KEY];
}
return null;
}
/**
* Set csrf token cookie
*
* @param $token
*/
public function setToken($token) {
setcookie(self::CSRF_KEY, $token, time() + 60 * 120, '/');
}
/**
* Get csrf token
* @return string|null
*/
public function getToken(){
if(isset($_COOKIE[self::CSRF_KEY])) {
return $_COOKIE[self::CSRF_KEY];
}
return null;
}
/**
* Returns whether the csrf token has been defined
* @return bool
*/
public function hasToken()
{
return isset($_COOKIE[static::CSRF_KEY]);
}
}
+6
View File
@@ -0,0 +1,6 @@
<?php
namespace Pecee\Exception;
class RouterException extends \Exception
{
}
@@ -1,4 +1,6 @@
<?php
namespace Pecee\Exception;
class TokenMismatchException extends \Exception {}
class TokenMismatchException extends \Exception
{
}
+17
View File
@@ -0,0 +1,17 @@
<?php
namespace Pecee\Handler;
use Pecee\Http\Request;
use Pecee\SimpleRouter\RouterEntry;
interface IExceptionHandler
{
/**
* @param Request $request
* @param RouterEntry|null $route
* @param \Exception $error
* @return Request|null
*/
public function handleError(Request $request, RouterEntry &$route = null, \Exception $error);
}
+211
View File
@@ -0,0 +1,211 @@
<?php
namespace Pecee\Http\Input;
use Pecee\Http\Request;
class Input
{
/**
* @var \Pecee\Http\Input\InputCollection
*/
public $get;
/**
* @var \Pecee\Http\Input\InputCollection
*/
public $post;
/**
* @var \Pecee\Http\Input\InputCollection
*/
public $file;
/**
* @var Request
*/
protected $request;
public function __construct(Request $request)
{
$this->request = $request;
$this->setGet();
$this->setPost();
$this->setFile();
}
/**
* Get all get/post items
* @param array|null $filter Only take items in filter
* @return array
*/
public function all(array $filter = null)
{
$output = $_POST;
if ($this->request->getMethod() === 'post') {
$contents = file_get_contents('php://input');
if (stripos(trim($contents), '{') === 0) {
$output = json_decode($contents, true);
if ($output === false) {
$output = array();
}
}
}
$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;
}
public function getObject($index, $default = null)
{
$key = (strpos($index, '[') > -1) ? substr($index, strpos($index, '[') + 1, strpos($index, ']') - strlen($index)) : null;
$index = (strpos($index, '[') > -1) ? substr($index, 0, strpos($index, '[')) : $index;
$element = $this->get->findFirst($index);
if ($element !== null) {
return ($key !== null) ? $element[$key] : $element;
}
if ($this->request->getMethod() !== 'get') {
$element = $this->post->findFirst($index);
if ($element !== null) {
return ($key !== null) ? $element[$key] : $element;
}
$element = $this->file->findFirst($index);
if ($element !== null) {
return ($key !== null) ? $element[$key] : $element;
}
}
return $default;
}
/**
* Get input element value matching index
* @param string $index
* @param string|null $default
* @return string|null
*/
public function get($index, $default = null)
{
$item = $this->getObject($index);
if ($item !== null) {
if ($item instanceof InputCollection || $item instanceof InputFile) {
return $item;
}
return (trim($item->getValue()) === '') ? $default : $item->getValue();
}
return $default;
}
public function exists($index)
{
return ($this->getObject($index) !== null);
}
public function setGet()
{
$this->get = new InputCollection();
if (count($_GET) > 0) {
foreach ($_GET as $key => $get) {
if (is_array($get) === false) {
$this->get->{$key} = new InputItem($key, $get);
continue;
}
$output = new InputCollection();
foreach ($get as $k => $g) {
$output->{$k} = new InputItem($k, $g);
}
$this->get->{$key} = $output;
}
}
}
public function setPost()
{
$this->post = new InputCollection();
$postVars = $_POST;
if (in_array($this->request->getMethod(), ['put', 'patch', 'delete']) === true) {
parse_str(file_get_contents('php://input'), $postVars);
}
if (count($postVars) > 0) {
foreach ($postVars as $key => $post) {
if (is_array($post) === false) {
$this->post->{strtolower($key)} = new InputItem($key, $post);
continue;
}
$output = new InputCollection();
foreach ($post as $k => $p) {
$output->{$k} = new InputItem($k, $p);
}
$this->post->{strtolower($key)} = $output;
}
}
}
public function setFile()
{
$this->file = new InputCollection();
if (count($_FILES) > 0) {
foreach ($_FILES as $key => $values) {
// Handle array input
if (is_array($values['name']) === false && trim($values['error']) !== '4') {
$values['index'] = $key;
$this->file->{strtolower($key)} = InputFile::createFromArray($values);
continue;
}
$output = new InputCollection();
foreach ($values['name'] as $k => $val) {
if (trim($val['error'][$k]) !== '4') {
$output->{$k} = InputFile::createFromArray([
'index' => $k,
'error' => $val['error'][$k],
'tmp_name' => $val['tmp_name'][$k],
'type' => $val['type'][$k],
'size' => $val['size'][$k],
'name' => $val['name'][$k]
]);
}
}
$this->file->{strtolower($key)} = $output;
}
}
}
}
+94
View File
@@ -0,0 +1,94 @@
<?php
namespace Pecee\Http\Input;
class InputCollection implements \IteratorAggregate
{
protected $data = array();
/**
* Search for input element matching index.
*
* @param string $index
* @param string|null $default
* @return InputItem|mixed
*/
public function findFirst($index, $default = null)
{
if (count($this->data) > 0) {
if (isset($this->data[$index])) {
return $this->data[$index];
}
foreach ($this->data as $key => $input) {
if (strtolower($index) === strtolower($key)) {
return $input;
}
}
}
return $default;
}
/**
* Get input element value matching index
*
* @param string $index
* @param string|null $default
* @return string|null
*/
public function get($index, $default = null)
{
$input = $this->findFirst($index);
if($input !== null && trim($input->getValue()) !== '') {
return $input->getValue();
}
return $default;
}
public function getValue($index, $default = null) {
}
/**
* @param string $index
* @throws \InvalidArgumentException
* @return InputItem
*/
public function __get($index)
{
$item = $this->findFirst($index);
// Ensure that item are always available
if ($item === null) {
$this->data[$index] = new InputItem($index, null);
return $this->data[$index];
}
return $item;
}
public function __set($index, $value)
{
$this->data[$index] = $value;
}
public function getData()
{
return $this->data;
}
/**
* Retrieve an external iterator
* @link http://php.net/manual/en/iteratoraggregate.getiterator.php
* @return \Traversable An instance of an object implementing <b>Iterator</b> or
* <b>Traversable</b>
* @since 5.0.0
*/
public function getIterator()
{
return new \ArrayIterator($this->data);
}
}
+136
View File
@@ -0,0 +1,136 @@
<?php
namespace Pecee\Http\Input;
class InputFile extends InputItem
{
public $size;
public $type;
public $error;
public $tmpName;
/**
* @return string
*/
public function getSize()
{
return $this->size;
}
/**
* @return string
*/
public function getType()
{
return $this->type;
}
/**
* @return string
*/
public function getError()
{
return $this->error;
}
public function getMime()
{
return $this->getType();
}
/**
* @return string
*/
public function getTmpName()
{
return $this->tmpName;
}
public function getExtension()
{
return pathinfo($this->getName(), PATHINFO_EXTENSION);
}
public function move($destination)
{
return move_uploaded_file($this->tmpName, $destination);
}
public function getContents()
{
return file_get_contents($this->tmpName);
}
/**
* Set file temp. name
* @param string $name
* @return static $this
*/
public function setTmpName($name)
{
$this->tmpName = $name;
return $this;
}
/**
* Set file size
* @param int $size
* @return static $this
*/
public function setSize($size)
{
$this->size = $size;
return $this;
}
/**
* Set type
* @param string $type
* @return static $this
*/
public function setType($type)
{
$this->type = $type;
return $this;
}
/**
* Set error
* @param int $error
* @return static $this
*/
public function setError($error)
{
$this->error = $error;
return $this;
}
/**
* Create from array
* @param array $values
* @return static
*/
public static function createFromArray(array $values)
{
if(!isset($values['index'])) {
throw new \InvalidArgumentException('Index key is required');
}
$input = new static($values['index']);
$input->setTmpName((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->setError((isset($values['tmp_name']) ? $values['tmp_name'] : null));
return $input;
}
/**
* @return string
*/
public function getValue()
{
return $this->tmpName;
}
}
+70
View File
@@ -0,0 +1,70 @@
<?php
namespace Pecee\Http\Input;
class InputItem
{
public $index;
public $name;
public $value;
public function __construct($index, $value = null)
{
$this->index = $index;
$this->value = $value;
// Make the name human friendly, by replace _ with space
$this->name = ucfirst(str_replace('_', ' ', $this->index));
}
/**
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* @return string
*/
public function getValue()
{
return $this->value;
}
/**
* @return string
*/
public function getIndex()
{
return $this->index;
}
/**
* Set input name
* @param string $name
* @return static $this
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Set input value
* @param string $value
* @return static $this
*/
public function setValue($value)
{
$this->value = $value;
return $this;
}
public function __toString()
{
return (string)$this->value;
}
}
+70 -43
View File
@@ -4,65 +4,92 @@ namespace Pecee\Http\Middleware;
use Pecee\CsrfToken;
use Pecee\Exception\TokenMismatchException;
use Pecee\Http\Request;
use Pecee\SimpleRouter\RouterEntry;
class BaseCsrfVerifier implements IMiddleware {
class BaseCsrfVerifier implements IMiddleware
{
const POST_KEY = 'csrf-token';
const HEADER_KEY = 'X-CSRF-TOKEN';
const POST_KEY = 'csrf-token';
const HEADER_KEY = 'X-CSRF-TOKEN';
protected $except;
protected $csrfToken;
protected $token;
protected $except;
protected $csrfToken;
public function __construct()
{
$this->csrfToken = new CsrfToken();
// Generate or get the CSRF-Token from Cookie.
$this->token = (!$this->hasToken()) ? $this->generateToken() : $this->csrfToken->getToken();
}
public function __construct() {
$this->csrfToken = new CsrfToken();
}
/**
* Check if the url matches the urls in the except property
* @param Request $request
* @return bool
*/
protected function skip(Request $request)
{
if ($this->except === null || is_array($this->except) === false) {
return false;
}
/**
* Check if the url matches the urls in the except property
* @param Request $request
* @return bool
*/
protected function skip(Request $request) {
foreach ($this->except as $url) {
$url = rtrim($url, '/');
if ($url[strlen($url) - 1] === '*') {
$url = rtrim($url, '*');
$skip = (stripos($request->getUri(), $url) === 0);
} else {
$skip = ($url === rtrim($request->getUri(), '/'));
}
if($this->except === null || !is_array($this->except)) {
return false;
}
if ($skip) {
return true;
}
}
foreach($this->except as $url) {
$url = rtrim($url, '/');
if($url[strlen($url)-1] === '*') {
$url = rtrim($url, '*');
$skip = (stripos($request->getUri(), $url) === 0);
} else {
$skip = ($url === rtrim($request->getUri(), '/'));
}
return false;
}
if($skip) {
return true;
}
}
public function handle(Request $request, RouterEntry &$route = null)
{
return false;
}
if ($request->getMethod() !== 'get' && !$this->skip($request)) {
public function handle(Request $request) {
$token = $request->getInput()->post->get(static::POST_KEY);
if($request->getMethod() != 'get' && !$this->skip($request)) {
// If the token is not posted, check headers for valid x-csrf-token
if ($token === null) {
$token = $request->getHeader(static::HEADER_KEY);
}
$token = (isset($_POST[self::POST_KEY])) ? $_POST[self::POST_KEY] : null;
if (!$this->csrfToken->validate($token)) {
throw new TokenMismatchException('Invalid csrf-token.');
}
// If the token is not posted, check headers for valid x-csrf-token
if($token === null) {
$token = $request->getHeader(self::HEADER_KEY);
}
}
if( !$this->csrfToken->validate( $token ) ) {
throw new TokenMismatchException('Invalid csrf-token.');
}
}
}
public function generateToken()
{
$token = $this->csrfToken->generateToken();
$this->csrfToken->setToken($token);
return $token;
}
}
public function hasToken()
{
if ($this->token != null) {
return true;
}
return $this->csrfToken->hasToken();
}
public function getToken()
{
return $this->token;
}
}
+10 -2
View File
@@ -2,7 +2,15 @@
namespace Pecee\Http\Middleware;
use Pecee\Http\Request;
use Pecee\SimpleRouter\RouterEntry;
interface IMiddleware
{
/**
* @param Request $request
* @param RouterEntry $route
* @return Request|null
*/
public function handle(Request $request, RouterEntry &$route);
interface IMiddleware {
public function handle(Request $request);
}
+183 -110
View File
@@ -1,133 +1,206 @@
<?php
namespace Pecee\Http;
class Request {
use Pecee\Http\Input\Input;
protected static $instance;
class Request
{
protected $data = array();
protected $headers;
protected $host;
protected $uri;
protected $method;
protected $input;
protected $data;
protected $uri;
protected $host;
protected $method;
protected $headers;
public function __construct()
{
$this->parseHeaders();
$this->input = new Input($this);
$this->host = $this->getHeader('http-host');;
$this->uri = $this->getHeader('request-uri');
$this->method = strtolower($this->input->post->findFirst('_method', $this->getHeader('request-method')));
}
/**
* Return new instance
* @return static
*/
public static function getInstance() {
if(self::$instance === null) {
self::$instance = new static();
}
return self::$instance;
}
protected function parseHeaders()
{
$this->headers = array();
public function __construct() {
$this->data = array();
$this->host = $_SERVER['HTTP_HOST'];
$this->uri = $_SERVER['REQUEST_URI'];
$this->method = (isset($_POST['_method'])) ? strtolower($_POST['_method']) : strtolower($_SERVER['REQUEST_METHOD']);
$this->headers = array_change_key_case(getallheaders(), CASE_LOWER);
}
foreach ($_SERVER as $name => $value) {
$this->headers[strtolower($name)] = $value;
$this->headers[strtolower(str_replace('_', '-', $name))] = $value;
}
}
public function getIsSecure() {
return isset($_SERVER['HTTPS']) ? true : (isset($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] === 443);
}
public function isSecure()
{
if ($this->getHeader('http-x-forwarded-proto') === 'https' || $this->getHeader('https') !== null || $this->getHeader('server-port') === 443) {
return true;
}
/**
* @return string
*/
public function getUri() {
return $this->uri;
}
return false;
}
/**
* @return string
*/
public function getHost() {
return $this->host;
}
/**
* @return string
*/
public function getUri()
{
return $this->uri;
}
/**
* @return string
*/
public function getMethod() {
return $this->method;
}
/**
* @return string
*/
public function getHost()
{
return $this->host;
}
/**
* Get http basic auth user
* @return string|null
*/
public function getUser() {
return (isset($_SERVER['PHP_AUTH_USER'])) ? $_SERVER['PHP_AUTH_USER']: null;
}
/**
* @return string
*/
public function getMethod()
{
return $this->method;
}
/**
* Get http basic auth password
* @return string|null
*/
public function getPassword() {
return (isset($_SERVER['PHP_AUTH_PW'])) ? $_SERVER['PHP_AUTH_PW']: null;
}
/**
* Get http basic auth user
* @return string|null
*/
public function getUser()
{
return $this->getHeader('php-auth-user');
}
/**
* Get headers
* @return array
*/
public function getHeaders() {
return $this->headers;
}
/**
* Get http basic auth password
* @return string|null
*/
public function getPassword()
{
return $this->getHeader('php-auth-pw');
}
/**
* Get id address
* @return string
*/
public function getIp() {
return isset($_SERVER['HTTP_X_FORWARDED_FOR']) ? $_SERVER['HTTP_X_FORWARDED_FOR'] : $_SERVER['REMOTE_ADDR'];
}
/**
* Get all headers
* @return array
*/
public function getHeaders()
{
return $this->headers;
}
/**
* Get referer
* @return string
*/
public function getReferer() {
return isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '';
}
/**
* Get id address
* @return string
*/
public function getIp()
{
if ($this->getHeader('http-cf-connecting-ip') !== null) {
return $this->getHeader('http-cf-connecting-ip');
}
/**
* Get user agent
* @return string
*/
public function getUserAgent() {
return isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '';
}
if ($this->getHeader('http-x-forwarded-for') !== null && strlen($this->getHeader('http-x-forwarded-for'))) {
return $this->getHeader('http-x-forwarded_for');
}
/**
* Get header value by name
* @param string $name
* @return string|null
*/
public function getHeader($name) {
return (isset($this->headers[strtolower($name)])) ? $this->headers[strtolower($name)] : null;
}
return $this->getHeader('remote-addr');
}
/**
* Get request input or default value
* @param string $name
* @param string $defaultValue
* @return mixed
*/
public function getInput($name, $defaultValue) {
return (isset($_REQUEST[$name]) ? $_REQUEST[$name] : $defaultValue);
}
/**
* Get referer
* @return string
*/
public function getReferer()
{
return $this->getHeader('http-referer');
}
public function __set($name, $value = null) {
$this->data[$name] = $value;
}
/**
* Get user agent
* @return string
*/
public function getUserAgent()
{
return $this->getHeader('http-user-agent');
}
public function __get($name) {
return isset($this->data[$name]) ? $this->data[$name] : null;
}
/**
* Get header value by name
*
* @param string $name
* @param object|null $defaultValue
*
* @return string|null
*/
public function getHeader($name, $defaultValue = null)
{
return isset($this->headers[strtolower($name)]) ? $this->headers[strtolower($name)] : $defaultValue;
}
/**
* Get input class
* @return Input
*/
public function getInput()
{
return $this->input;
}
/**
* Is format accepted
*
* @param string $format
*
* @return bool
*/
public function isFormatAccepted($format)
{
return ($this->getHeader('http-accept') !== null && stripos($this->getHeader('http-accept'), $format) > -1);
}
/**
* Get accept formats
* @return array
*/
public function getAcceptFormats()
{
return explode(',', $this->getHeader('http-accept'));
}
/**
* @param string $uri
*/
public function setUri($uri)
{
$this->uri = $uri;
}
/**
* @param string $host
*/
public function setHost($host)
{
$this->host = $host;
}
/**
* @param string $method
*/
public function setMethod($method)
{
$this->method = $method;
}
public function __set($name, $value = null)
{
$this->data[$name] = $value;
}
public function __get($name)
{
return isset($this->data[$name]) ? $this->data[$name] : null;
}
}
+100 -80
View File
@@ -1,98 +1,118 @@
<?php
namespace Pecee\Http;
class Response {
class Response
{
protected $request;
/**
* Set the http status code
*
* @param int $code
* @return self $this
*/
public function httpCode($code) {
http_response_code($code);
return $this;
}
public function __construct(Request $request)
{
$this->request = $request;
}
/**
* Redirect the response
*
* @param string $url
*/
public function redirect($url) {
$this->header('Location: ' . $url);
die();
}
/**
* Set the http status code
*
* @param int $code
* @return static
*/
public function httpCode($code)
{
http_response_code($code);
return $this;
}
public function refresh() {
$this->redirect(Request::getInstance()->getUri());
}
/**
* Redirect the response
*
* @param string $url
* @param int $httpCode
*/
public function redirect($url, $httpCode = null)
{
if ($httpCode !== null) {
$this->httpCode($httpCode);
}
/**
* Add http authorisation
* @param string $name
* @return self $this
*/
public function auth($name = '') {
$this->headers([
'WWW-Authenticate: Basic realm="' . $name . '"',
'HTTP/1.0 401 Unauthorized'
]);
return $this;
}
$this->header('location: ' . $url);
die();
}
public function cache($eTag, $lastModified = 2592000) {
public function refresh()
{
$this->redirect($this->request->getUri());
}
$this->headers([
'Cache-Control: public',
'Last-Modified: ' . gmdate("D, d M Y H:i:s", $lastModified) . ' GMT',
'Etag: ' . $eTag
]);
/**
* Add http authorisation
* @param string $name
* @return static
*/
public function auth($name = '')
{
$this->headers([
'WWW-Authenticate: Basic realm="' . $name . '"',
'HTTP/1.0 401 Unauthorized'
]);
return $this;
}
if(isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) === $lastModified ||
isset($_SERVER['HTTP_IF_NONE_MATCH']) && $_SERVER['HTTP_IF_NONE_MATCH'] === $eTag) {
public function cache($eTag, $lastModified = 2592000)
{
$this->headers([
'HTTP/1.1 304 Not Modified'
]);
$this->headers([
'Cache-Control: public',
'Last-Modified: ' . gmdate("D, d M Y H:i:s", $lastModified) . ' GMT',
'Etag: ' . $eTag
]);
exit();
}
if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) === $lastModified ||
isset($_SERVER['HTTP_IF_NONE_MATCH']) && $_SERVER['HTTP_IF_NONE_MATCH'] === $eTag
) {
return $this;
}
$this->headers([
'HTTP/1.1 304 Not Modified'
]);
/**
* Json encode array
* @param array $value
*/
public function json(array $value) {
$this->header('Content-type: application/json');
echo json_encode($value);
die();
}
exit();
}
/**
* Add header to response
* @param string $value
* @return self $this
*/
public function header($value) {
header($value);
return $this;
}
return $this;
}
/**
* Add multiple headers to response
* @param array $headers
* @return self $this
*/
public function headers(array $headers) {
foreach($headers as $header) {
header($header);
}
return $this;
}
/**
* Json encode array
* @param array $value
*/
public function json(array $value)
{
$this->header('Content-type: application/json');
echo json_encode($value);
die();
}
/**
* Add header to response
* @param string $value
* @return static
*/
public function header($value)
{
header($value);
return $this;
}
/**
* Add multiple headers to response
* @param array $headers
* @return static
*/
public function headers(array $headers)
{
foreach ($headers as $header) {
header($header);
}
return $this;
}
}
@@ -0,0 +1,13 @@
<?php
namespace Pecee\SimpleRouter;
interface IControllerRoute
{
public function getController();
public function setController($controller);
public function getMethod();
public function setMethod($method);
}
@@ -0,0 +1,9 @@
<?php
namespace Pecee\SimpleRouter;
interface ILoadableRoute
{
public function getUrl();
public function setUrl($url);
}
-7
View File
@@ -1,7 +0,0 @@
<?php
namespace Pecee\SimpleRouter;
interface IRouteEntry {
}
+97
View File
@@ -0,0 +1,97 @@
<?php
namespace Pecee\SimpleRouter;
abstract class LoadableRoute extends RouterEntry implements ILoadableRoute
{
const PARAMETERS_REGEX_MATCH = '%s([\w\-\_]*?)\%s{0,1}%s';
const PARAMETER_MODIFIERS = '{}';
const PARAMETER_OPTIONAL_SYMBOL = '?';
protected $url;
protected $alias;
public function getUrl()
{
return $this->url;
}
/**
* Set url
*
* @param string $url
* @return static
*/
public function setUrl($url)
{
$this->url = ($url === '/') ? '/' : '/' . trim($url, '/') . '/';
$regex = sprintf(static::PARAMETERS_REGEX_MATCH, static::PARAMETER_MODIFIERS[0], static::PARAMETER_OPTIONAL_SYMBOL, static::PARAMETER_MODIFIERS[1]);
if (preg_match_all('/' . $regex . '/is', $this->url, $matches)) {
foreach ($matches[1] as $key) {
$this->parameters[$key] = null;
}
}
return $this;
}
/**
* Get alias for the url which can be used when getting the url route.
* @return string|array
*/
public function getAlias()
{
return $this->alias;
}
/**
* Check if route has given alias.
*
* @param string $name
* @return bool
*/
public function hasAlias($name)
{
if ($this->getAlias() !== null) {
if (is_array($this->getAlias()) === true) {
foreach ($this->getAlias() as $alias) {
if (strtolower($alias) === strtolower($name)) {
return true;
}
}
}
return strtolower($this->getAlias()) === strtolower($name);
}
return false;
}
/**
* Set the url alias for easier getting the url route.
* @param string|array $alias
* @return static
*/
public function setAlias($alias)
{
$this->alias = $alias;
return $this;
}
/**
* Merge with information from another route.
*
* @param array $values
* @return static
*/
public function merge(array $values)
{
// Change as to alias
if (isset($values['as'])) {
$this->setAlias($values['as']);
}
parent::merge($values);
return $this;
}
}
+533 -345
View File
@@ -1,373 +1,561 @@
<?php
namespace Pecee\SimpleRouter;
use Pecee\Exception\RouterException;
use Pecee\Handler\IExceptionHandler;
use Pecee\Http\Middleware\BaseCsrfVerifier;
use Pecee\Http\Request;
use Pecee\Http\Response;
class RouterBase
{
/**
* @var static
*/
protected static $instance;
/**
* Current request
* @var Request
*/
protected $request;
/**
* Response
* @var Response
*/
protected $response;
/**
* Used to keep track of whether or not a should should be added to
* the backstack-list for group-processing or not.
* @var bool
*/
protected $processingRoute;
/**
* All added routes
* @var array
*/
protected $routes;
/**
* List of
* @var array
*/
protected $controllerUrlMap;
/**
* Backstack array used to keep track of sub-routes
* @var array
*/
protected $backStack;
/**
* List of added bootmanagers
* @var array
*/
protected $bootManagers;
/**
* Csrf verifier class
* @var BaseCsrfVerifier
*/
protected $csrfVerifier;
/**
* Get exception handlers
* @var array
*/
protected $exceptionHandlers;
/**
* The current loaded route
* @var RouterRoute|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();
}
public function reset()
{
$this->processingRoute = false;
$this->request = new Request();
$this->response = new Response($this->request);
$this->routes = array();
$this->bootManagers = array();
$this->backStack = array();
$this->controllerUrlMap = array();
$this->exceptionHandlers = array();
}
/**
* Add route
* @param RouterEntry $route
* @return RouterEntry
*/
public function addRoute(RouterEntry $route)
{
if ($this->processingRoute) {
$this->backStack[] = $route;
} else {
$this->routes[] = $route;
}
return $route;
}
protected function processRoutes(array $routes, array $settings = array(), array $prefixes = array(), RouterEntry $parent = null)
{
// Loop through each route-request
/* @var $route RouterEntry */
foreach ($routes as $route) {
$newPrefixes = $prefixes;
$newSettings = $settings;
if($parent !== null) {
$route->setParent($parent);
}
if ($route instanceof RouterGroup) {
if ($route->getCallback() !== null && is_callable($route->getCallback())) {
$this->processingRoute = true;
$route->renderRoute($this->request);
$this->processingRoute = false;
if ($route->matchRoute($this->request)) {
// Add ExceptionHandler
if (count($route->getExceptionHandlers()) > 0) {
$this->exceptionHandlers = array_merge($route->getExceptionHandlers(), $this->exceptionHandlers);
}
}
}
}
if ($route instanceof RouterGroup) {
$newPrefixes[] = trim($route->getPrefix(), '/');
$newSettings = array_merge($settings, $route->toArray());
} else {
if (count($settings)) {
$route->merge($settings);
}
}
if ($route instanceof ILoadableRoute) {
if (count($prefixes)) {
$route->setUrl(trim(join('/', $prefixes) . $route->getUrl(), '/'));
}
$this->controllerUrlMap[] = $route;
}
if (count($this->backStack) > 0) {
$backStack = $this->backStack;
$this->backStack = [];
// Route any routes added to the backstack
$this->processRoutes($backStack, $newSettings, $newPrefixes, $route);
}
}
}
public function routeRequest($rewrite = false)
{
$this->loadedRoute = null;
$routeNotAllowed = false;
try {
// Initialize boot-managers
if (count($this->bootManagers) > 0) {
/* @var $manager RouterBootManager */
foreach ($this->bootManagers as $manager) {
$this->request = $manager->boot($this->request);
if (!($this->request instanceof Request)) {
throw new RouterException('Custom router bootmanager "' . get_class($manager) . '" must return instance of Request.');
}
}
}
if ($rewrite === false) {
class RouterBase {
// Loop through each route-request
$this->processRoutes($this->routes);
protected static $instance;
if ($this->csrfVerifier !== null) {
protected $request;
protected $currentRoute;
protected $routes;
protected $processedRoutes;
protected $controllerUrlMap;
protected $backstack;
protected $loadedRoute;
protected $defaultNamespace;
protected $baseCsrfVerifier;
// Verify csrf token for request
$this->csrfVerifier->handle($this->request);
}
// TODO: make interface for controller routers, so they can be easily detected
// TODO: clean up - cut some of the methods down to smaller pieces
$this->originalUrl = $this->request->getUri();
}
public function __construct() {
$this->routes = array();
$this->backstack = array();
$this->controllerUrlMap = array();
$this->request = Request::getInstance();
}
/* @var $route RouterEntry */
foreach ($this->controllerUrlMap as $route) {
public function addRoute(RouterEntry $route) {
if($this->currentRoute !== null) {
$this->backstack[] = $route;
} else {
$this->routes[] = $route;
}
}
if ($route->matchRoute($this->request)) {
protected function processRoutes(array $routes, array $settings = array(), array $prefixes = array(), $backStack = false, $group = null) {
// Loop through each route-request
if (count($route->getRequestMethods()) > 0 && !in_array($this->request->getMethod(), $route->getRequestMethods())) {
$routeNotAllowed = true;
continue;
}
$activeGroup = null;
$routesCount = count($routes);
$mergedSettings = array();
$this->loadedRoute = $route;
$this->loadedRoute->loadMiddleware($this->request, $this->loadedRoute);
/* @var $route RouterEntry */
for($i = 0; $i < $routesCount; $i++) {
if ($this->request->getUri() !== $this->originalUrl && !in_array($this->request->getUri(), $this->routeRewrites)) {
$this->routeRewrites[] = $this->request->getUri();
$this->routeRequest(true);
return;
}
$route = $routes[$i];
$routeNotAllowed = false;
$this->request->setUri($this->originalUrl);
$this->loadedRoute->renderRoute($this->request);
$route->setGroup($group);
break;
}
}
if($this->defaultNamespace && !$route->getNamespace()) {
$namespace = null;
if ($route->getNamespace()) {
$namespace = $this->defaultNamespace . '\\' . $route->getNamespace();
} else {
$namespace = $this->defaultNamespace;
}
} catch (\Exception $e) {
$this->handleException($e);
}
$route->setNamespace($namespace);
}
if ($routeNotAllowed) {
$this->handleException(new RouterException('Route or method not allowed', 403));
}
$newPrefixes = $prefixes;
if ($this->loadedRoute === null) {
$this->handleException(new RouterException(sprintf('Route not found: %s', $this->request->getUri()), 404));
}
}
if($route->getPrefix()) {
array_push($newPrefixes, rtrim($route->getPrefix(), '/'));
}
$route->addSettings($settings);
if(!($route instanceof RouterGroup)) {
if(is_array($newPrefixes) && count($newPrefixes) && $backStack) {
$route->setUrl( join('/', $newPrefixes) . $route->getUrl() );
}
$this->controllerUrlMap[] = $route;
}
$this->currentRoute = $route;
if($route instanceof RouterGroup && is_callable($route->getCallback())) {
$route->renderRoute($this->request);
$activeGroup = $route;
$mergedSettings = array_merge($route->getMergeableSettings(), $settings);
}
$this->currentRoute = null;
if(count($this->backstack)) {
$backStack = $this->backstack;
$this->backstack = array();
// Route any routes added to the backstack
$this->processRoutes($backStack, $mergedSettings, $newPrefixes, true, $activeGroup);
}
}
}
public function routeRequest() {
// Verify csrf token for request
if($this->baseCsrfVerifier !== null) {
/* @var $csrfVerifier BaseCsrfVerifier */
$csrfVerifier = $this->baseCsrfVerifier;
$csrfVerifier = new $csrfVerifier();
$csrfVerifier->handle($this->request);
}
// Loop through each route-request
$this->processRoutes($this->routes);
$routeNotAllowed = false;
$max = count($this->controllerUrlMap);
/* @var $route RouterEntry */
for($i = 0; $i < $max; $i++) {
$route = $this->controllerUrlMap[$i];
$routeMatch = $route->matchRoute($this->request);
if($routeMatch) {
if(count($route->getRequestMethods()) && !in_array($this->request->getMethod(), $route->getRequestMethods())) {
$routeNotAllowed = true;
continue;
}
$routeNotAllowed = false;
$this->loadedRoute = $route;
$route->loadMiddleware($this->request);
$route->renderRoute($this->request);
break;
}
}
if($routeNotAllowed) {
throw new RouterException('Route or method not allowed', 403);
}
if(!$this->loadedRoute) {
throw new RouterException(sprintf('Route not found: %s', $this->request->getUri()), 404);
}
}
/**
* @return string
*/
public function getDefaultNamespace(){
return $this->defaultNamespace;
}
/**
* @param string $defaultNamespace
*/
public function setDefaultNamespace($defaultNamespace) {
$this->defaultNamespace = $defaultNamespace;
}
/**
* @return RouterEntry
*/
public function getLoadedRoute() {
if(!($this->loadedRoute instanceof RouterGroup)) {
return $this->loadedRoute;
}
return null;
}
/**
* @return array
*/
public function getBackstack() {
return $this->backstack;
}
/**
* @return RouterEntry
*/
public function getCurrentRoute(){
return $this->currentRoute;
}
/**
* @return array
*/
public function getRoutes(){
return $this->routes;
}
/**
* Get current request
*
* @return Request
*/
public function getRequest() {
return $this->request;
}
/**
* Get base csrf verifier class
* @return BaseCsrfVerifier
*/
public function getBaseCsrfVerifier() {
return $this->baseCsrfVerifier;
}
/**
* Set base csrf verifier class
*
* @param BaseCsrfVerifier $baseCsrfVerifier
* @return self
*/
public function setBaseCsrfVerifier(BaseCsrfVerifier $baseCsrfVerifier) {
$this->baseCsrfVerifier = $baseCsrfVerifier;
return $this;
}
public function arrayToParams(array $getParams = null, $includeEmpty = true) {
if (is_array($getParams) && count($getParams) > 0) {
foreach ($getParams as $key => $val) {
if (!empty($val) || empty($val) && $includeEmpty) {
$getParams[$key] = $key . '=' . $val;
}
}
return join('&', $getParams);
}
return '';
}
protected function processUrl($route, $method = null, $parameters = null, $getParams = null) {
$url = '/' . trim($route->getUrl(), '/');
if(($route instanceof RouterController || $route instanceof RouterResource) && $method !== null) {
$url .= $method;
}
if($route instanceof RouterController || $route instanceof RouterResource) {
if(count($parameters)) {
$url .= join('/', $parameters);
}
} else {
/* @var $route RouterEntry */
$params = $route->getParameters();
if(count($params)) {
$i = 0;
foreach($params as $param => $value) {
$value = (isset($parameters[$param])) ? $parameters[$param] : $value;
$url = str_ireplace(array('{' . $param. '}', '{' . $param. '?}'), $value, $url);
$i++;
}
} else {
// If no parameters are specified in the route, assume that the provided parameters should be used.
if(count($parameters)) {
$url = rtrim($url, '/') . '/' . join('/', $parameters);
}
}
}
$url = rtrim($url, '/') . '/';
if($getParams !== null && count($getParams)) {
$url .= '?' . $this->arrayToParams($getParams);
}
return $url;
}
public function getRoute($controller = null, $parameters = null, $getParams = null) {
if($parameters !== null && !is_array($parameters)) {
throw new \InvalidArgumentException('Invalid type for parameter. Must be array or null');
}
if($getParams !== null && !is_array($getParams)) {
throw new \InvalidArgumentException('Invalid type for getParams. Must be array or null');
}
// Return current route if no options has been specified
if($controller === null && $parameters === null && $this->loadedRoute !== null) {
$getParams = (is_array($getParams)) ? array_merge($_GET, $getParams) : $_GET;
$url = parse_url(Request::getInstance()->getUri());
$url = $url['path'];
if(count($getParams)) {
$url .= '?' . $this->arrayToParams($getParams);
}
return $url;
}
$c = '';
$method = null;
$max = count($this->controllerUrlMap);
/* @var $route RouterRoute */
for($i = 0; $i < $max; $i++) {
$route = $this->controllerUrlMap[$i];
// Check an alias exist, if the matches - use it
if($route instanceof RouterRoute && strtolower($route->getAlias()) === strtolower($controller)) {
return $this->processUrl($route, $route->getMethod(), $parameters, $getParams);
}
if($route instanceof RouterRoute && !is_callable($route->getCallback()) && stripos($route->getCallback(), '@') !== false) {
$c = $route->getCallback();
} else if($route instanceof RouterController || $route instanceof RouterResource) {
$c = $route->getController();
}
if($c === $controller || strpos($c, $controller) === 0) {
return $this->processUrl($route, $route->getMethod(), $parameters, $getParams);
}
}
$c = '';
// No match has yet been found, let's try to guess what url that should be returned
for($i = 0; $i < $max; $i++) {
$route = $this->controllerUrlMap[$i];
if($route instanceof RouterRoute && !is_callable($route->getCallback()) && stripos($route->getCallback(), '@') !== false) {
$c = $route->getClass();
} else if($route instanceof RouterController || $route instanceof RouterResource) {
$c = $route->getController();
}
if(stripos($controller, '@') !== false) {
$tmp = explode('@', $controller);
$controller = $tmp[0];
$method = $tmp[1];
}
if($controller === $c) {
return $this->processUrl($route, $method, $parameters, $getParams);
}
}
$controller = ($controller === null) ? '/' : $controller;
$url = array($controller);
protected function handleException(\Exception $e)
{
/* @var $handler IExceptionHandler */
foreach ($this->exceptionHandlers as $handler) {
if(is_array($parameters)) {
foreach($parameters as $key => $value) {
array_push($url,$value);
}
}
$handler = new $handler();
$url = '/' . trim(join('/', $url), '/') . '/';
if (!($handler instanceof IExceptionHandler)) {
throw new RouterException('Exception handler must implement the IExceptionHandler interface.');
}
$request = $handler->handleError($this->request, $this->loadedRoute, $e);
if($getParams !== null && count($getParams)) {
$url .= '?' . $this->arrayToParams($getParams);
}
if ($request !== null && $request->getUri() !== $this->originalUrl && !in_array($request->getUri(), $this->routeRewrites)) {
$this->routeRewrites[] = $request->getUri();
$this->routeRequest(true);
return;
}
return $url;
}
}
public static function getInstance() {
if(self::$instance === null) {
self::$instance = new static();
}
return self::$instance;
}
throw $e;
}
public function arrayToParams(array $getParams = null, $includeEmpty = true)
{
if (is_array($getParams) === true && count($getParams) > 0) {
if ($includeEmpty === false) {
$getParams = array_filter($getParams, function ($item) {
return (!empty($item));
});
}
return '?' . http_build_query($getParams);
}
return '';
}
protected function processUrl(LoadableRoute $route, $method = null, $parameters = null, $getParams = null)
{
$domain = '';
$parent = $route->getParent();
$parameters = (array)$parameters;
if ($parent !== null && $parent instanceof RouterGroup && count($parent->getDomains()) > 0) {
$domain = $parent->getDomains();
$domain = '//' . $domain[0];
}
$url = $domain . '/' . trim($route->getUrl(), '/');
if ($route instanceof IControllerRoute && $method !== null) {
$url .= '/' . $method . '/';
if (count($parameters) > 0) {
$url .= join('/', (array)$parameters);
}
} else {
if ($parameters !== null && count($parameters) > 0) {
$params = array_merge($route->getParameters(), (array)$parameters);
} else {
$params = $route->getParameters();
}
$otherParams = array();
foreach ($params as $param => $value) {
$value = (isset($parameters[$param])) ? $parameters[$param] : $value;
$param1 = LoadableRoute::PARAMETER_MODIFIERS[0] . $param . LoadableRoute::PARAMETER_MODIFIERS[1];
$param2 = LoadableRoute::PARAMETER_MODIFIERS[0] . $param . LoadableRoute::PARAMETER_OPTIONAL_SYMBOL . LoadableRoute::PARAMETER_MODIFIERS[1];
if (stripos($url, $param1) !== false || stripos($url, $param) !== false) {
$url = str_ireplace([$param1, $param2], $value, $url);
} else {
$otherParams[$param] = $value;
}
}
$url = rtrim($url, '/') . '/' . join('/', $otherParams);
}
$url = rtrim($url, '/') . '/';
if ($getParams !== null) {
$url .= $this->arrayToParams($getParams);
}
return $url;
}
/**
* Find route by alias, class, callback or method.
*
* @param string $query
* @return LoadableRoute|null
*/
public function findRoute($query)
{
/* @var $route LoadableRoute */
foreach ($this->controllerUrlMap as $route) {
// Check an alias exist, if the matches - use it
// Matches either Router alias or controller alias.
if ($route->hasAlias($query)) {
return $route;
}
// Direct match to controller
if ($route instanceof IControllerRoute) {
if (strtolower($route->getController()) === strtolower($query)) {
return $route;
}
}
// Using @ is most definitely a controller@method or alias@method
if (strpos($query, '@') !== false) {
list($controller, $method) = array_map('strtolower', explode('@', $query));
if ($controller === strtolower($route->getClass()) && $method === strtolower($route->getMethod())) {
return $route;
}
}
// Use callback if it's not a function
if (strpos($query, '@') !== false && strpos($route->getCallback(), '@') !== false && !is_callable($route->getCallback())) {
if (strtolower($query) === strtolower($route->getClass())) {
return $route;
}
if (strtolower($route->getCallback()) === strtolower($query) || strpos($route->getCallback(), $query) === 0) {
return $route;
}
}
}
return null;
}
public function getRoute($controller = null, $parameters = null, $getParams = null)
{
if ($getParams !== null && is_array($getParams) === false) {
throw new \InvalidArgumentException('Invalid type for getParams. Must be array or null');
}
// Return current route if no options has been specified
if ($controller === null && $parameters === null) {
$getParams = ($getParams !== null) ? $getParams : $_GET;
$url = parse_url($this->request->getUri(), PHP_URL_PATH) . $this->arrayToParams($getParams);
return $url;
}
// If nothing is defined and a route is loaded we use that
if ($controller === null && $this->loadedRoute !== null) {
return $this->processUrl($this->loadedRoute, $this->loadedRoute->getMethod(), $parameters, $getParams);
}
$route = $this->findRoute($controller);
if ($route !== null) {
return $this->processUrl($route, $route->getMethod(), $parameters, $getParams);
}
// Using @ is most definitely a controller@method or alias@method
if (stripos($controller, '@') !== false) {
list($controller, $method) = explode('@', $controller);
/* @var $route LoadableRoute */
foreach ($this->controllerUrlMap as $route) {
if ($route->hasAlias($controller)) {
return $this->processUrl($route, $method, $parameters, $getParams);
}
// Match controllers either by: "alias @ method" or "controller@method"
if ($route instanceof IControllerRoute && strtolower($route->getController()) === strtolower($controller)) {
return $this->processUrl($route, $method, $parameters, $getParams);
}
}
}
$url = [($controller === null) ? '/' : $controller];
if ($parameters !== null && count($parameters) > 0) {
$url = array_merge($url, (array)$parameters);
}
$url = '/' . trim(join('/', $url), '/') . '/';
if ($getParams !== null) {
$url .= $this->arrayToParams($getParams);
}
return $url;
}
/**
* Get bootmanagers
* @return array
*/
public function getBootManagers()
{
return $this->bootManagers;
}
/**
* Set bootmanagers
* @param array $bootManagers
*/
public function setBootManagers(array $bootManagers)
{
$this->bootManagers = $bootManagers;
}
/**
* Add bootmanager
* @param RouterBootManager $bootManager
*/
public function addBootManager(RouterBootManager $bootManager)
{
$this->bootManagers[] = $bootManager;
}
/**
* @return array
*/
public function getRoutes()
{
return $this->routes;
}
/**
* Get current request
*
* @return Request
*/
public function getRequest()
{
return $this->request;
}
/**
* Get response
* @return Response
*/
public function getResponse()
{
return $this->response;
}
/**
* Get csrf verifier class
* @return BaseCsrfVerifier
*/
public function getCsrfVerifier()
{
return $this->csrfVerifier;
}
/**
* Set csrf verifier class
*
* @param BaseCsrfVerifier $csrfVerifier
* @return static
*/
public function setCsrfVerifier(BaseCsrfVerifier $csrfVerifier)
{
$this->csrfVerifier = $csrfVerifier;
return $this;
}
/**
* Get loaded route
* @return RouterRoute|null
*/
public function getLoadedRoute()
{
return $this->loadedRoute;
}
}
@@ -0,0 +1,9 @@
<?php
namespace Pecee\SimpleRouter;
use Pecee\Http\Request;
abstract class RouterBootManager
{
abstract public function boot(Request $request);
}
+81 -86
View File
@@ -1,115 +1,110 @@
<?php
namespace Pecee\SimpleRouter;
use Pecee\Exception\RouterException;
use Pecee\Http\Request;
class RouterController extends RouterEntry {
class RouterController extends LoadableRoute implements IControllerRoute
{
protected $defaultMethod = 'index';
protected $controller;
protected $method;
const DEFAULT_METHOD = 'index';
public function __construct($url, $controller)
{
$this->setUrl($url);
$this->controller = $controller;
}
protected $url;
protected $controller;
protected $method;
public function renderRoute(Request $request)
{
if ($this->getCallback() !== null && is_callable($this->getCallback())) {
public function __construct($url, $controller) {
parent::__construct();
$this->url = $url;
$this->controller = $controller;
}
// 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];
public function renderRoute(Request $request) {
if(is_object($this->getCallback()) && is_callable($this->getCallback())) {
$class = $this->loadClass($className);
$method = $request->getMethod() . ucfirst($controller[1]);
// 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];
if (!method_exists($class, $method)) {
throw new RouterException(sprintf('Method %s does not exist in class %s', $method, $className), 404);
}
$class = $this->loadClass($className);
$method = $request->getMethod() . ucfirst($controller[1]);
call_user_func_array(array($class, $method), $this->getParameters());
if (!method_exists($class, $method)) {
throw new RouterException(sprintf('Method %s does not exist in class %s', $method, $className), 404);
}
return $class;
}
call_user_func_array(array($class, $method), $this->getParameters());
return null;
}
return $class;
}
public function matchRoute(Request $request)
{
$url = parse_url(urldecode($request->getUri()), PHP_URL_PATH);
$url = rtrim($url, '/') . '/';
return null;
}
if (strtolower($url) == strtolower($this->url) || stripos($url, $this->url) === 0) {
public function matchRoute(Request $request) {
$url = parse_url($request->getUri());
$url = rtrim($url['path'], '/') . '/';
$strippedUrl = trim(str_ireplace($this->url, '/', $url), '/');
if(strtolower($url) == strtolower($this->url) || stripos($url, $this->url) === 0) {
$path = explode('/', $strippedUrl);
$strippedUrl = trim(str_ireplace($this->url, '/', $url), '/');
if (count($path) > 0) {
$path = explode('/', $strippedUrl);
$method = (!isset($path[0]) || trim($path[0]) === '') ? $this->defaultMethod : $path[0];
$this->method = $method;
if(count($path)) {
array_shift($path);
$this->parameters = $path;
$method = (!isset($path[0]) || trim($path[0]) === '') ? self::DEFAULT_METHOD : $path[0];
$this->method = $method;
// Set callback
$this->setCallback($this->controller . '@' . $this->method);
array_shift($path);
$this->parameters = $path;
return true;
}
}
// Set callback
$this->setCallback($this->controller . '@' . $this->method);
return null;
}
return true;
}
}
return null;
}
/**
* @return string
*/
public function getController()
{
return $this->controller;
}
/**
* @return string
*/
public function getUrl() {
return $this->url;
}
/**
* @param string $controller
* @return static
*/
public function setController($controller)
{
$this->controller = $controller;
return $this;
}
/**
* @param string $url
*/
public function setUrl($url) {
$url = rtrim($url, '/') . '/';
$this->url = $url;
}
/**
* @return string
*/
public function getMethod()
{
return $this->method;
}
/**
* @return string
*/
public function getController() {
return $this->controller;
}
/**
* @param string $controller
*/
public function setController($controller) {
$this->controller = $controller;
}
/**
* @return string
*/
public function getMethod() {
return $this->method;
}
/**
* @param string $method
*/
public function setMethod($method) {
$this->method = $method;
}
/**
* @param string $method
* @return static
*/
public function setMethod($method)
{
$this->method = $method;
return $this;
}
}
+434 -403
View File
@@ -1,411 +1,442 @@
<?php
namespace Pecee\SimpleRouter;
use Pecee\Exception\RouterException;
use Pecee\Http\Middleware\IMiddleware;
use Pecee\Http\Request;
abstract class RouterEntry {
const REQUEST_TYPE_POST = 'post';
const REQUEST_TYPE_GET = 'get';
const REQUEST_TYPE_PUT = 'put';
const REQUEST_TYPE_PATCH = 'patch';
const REQUEST_TYPE_DELETE = 'delete';
public static $allowedRequestTypes = array(
self::REQUEST_TYPE_DELETE,
self::REQUEST_TYPE_GET,
self::REQUEST_TYPE_POST,
self::REQUEST_TYPE_PUT,
self::REQUEST_TYPE_PATCH
);
protected $settings;
protected $callback;
public function __construct() {
$this->settings = array();
$this->settings['requestMethods'] = array();
$this->settings['parametersRegex'] = array();
$this->settings['parameters'] = array();
}
/**
* 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
* when using translations etc.
*
* @return string
*/
public function getIdentifier() {
if(strpos($this->callback, '@') !== false) {
return $this->callback;
}
return 'function_' . md5($this->callback);
}
/**
* @param string $callback
* @return self;
*/
public function setCallback($callback) {
$this->callback = $callback;
return $this;
}
/**
* @return mixed
*/
public function getCallback() {
return $this->callback;
}
public function getMethod() {
if(strpos($this->callback, '@') !== false) {
$tmp = explode('@', $this->callback);
return $tmp[1];
}
return null;
}
public function getClass() {
if(strpos($this->callback, '@') !== false) {
$tmp = explode('@', $this->callback);
return $tmp[0];
}
return null;
}
/**
* @param string $prefix
* @return self
*/
public function setPrefix($prefix) {
$this->prefix = '/' . trim($prefix, '/') . '/';
return $this;
}
/**
* @param string $middleware
* @return self
*/
public function setMiddleware($middleware) {
$this->middleware = $middleware;
return $this;
}
/**
* @param string $namespace
* @return self
*/
public function setNamespace($namespace) {
$this->namespace = $namespace;
return $this;
}
/**
* @return string
*/
public function getPrefix() {
return $this->prefix;
}
/**
* @return string
*/
public function getMiddleware() {
return $this->middleware;
}
/**
* @return string
*/
public function getNamespace() {
return $this->namespace;
}
/**
* @return array
*/
public function getSettings() {
return $this->settings;
}
/**
* @return mixed
*/
public function getParameters(){
return ($this->parameters === null) ? array() : $this->parameters;
}
/**
* @param mixed $parameters
* @return self
*/
public function setParameters($parameters) {
$this->parameters = $parameters;
return $this;
}
/**
* Add regular expression parameter match
*
* @param array $options
* @return self
*/
public function where(array $options) {
$this->parametersRegex = array_merge($this->parametersRegex, $options);
return $this;
}
/**
* Add regular expression match for url
*
* @param string $regex
* @return self
*/
public function match($regex) {
$this->regexMatch = $regex;
return $this;
}
/**
* Get settings that are allowed to be inherited by child routes.
*
* @return array
*/
public function getMergeableSettings() {
$settings = $this->settings;
/*if(isset($settings['middleware'])) {
unset($settings['middleware']);
}*/
if(isset($settings['prefix'])) {
unset($settings['prefix']);
}
return $settings;
}
/**
* @param array $settings
* @return self
*/
public function addSettings(array $settings = null) {
if(is_array($settings)) {
$this->settings = array_merge($this->settings, $settings);
}
return $this;
}
/**
* @param array $settings
* @return self
*/
public function setSettings($settings) {
$this->settings = $settings;
if(isset($settings['prefix'])) {
$this->setPrefix($settings['prefix']);
}
return $this;
}
/**
* Dynamically access settings value
*
* @param $name
* @return mixed|null
*/
public function __get($name) {
return (isset($this->settings[$name]) ? $this->settings[$name] : null);
}
/**
* Dynamicially set settings value
*
* @param string $name
* @param mixed|null $value
*/
public function __set($name, $value = null) {
$this->settings[$name] = $value;
}
protected function loadClass($name) {
if(!class_exists($name)) {
throw new RouterException(sprintf('Class %s does not exist', $name));
}
return new $name();
}
protected function parseParameters($route, $url, $parameterRegex = '[a-z0-9]*?') {
$parameterNames = array();
$regex = '';
$lastCharacter = '';
$isParameter = false;
$parameter = '';
// Use custom parameter regex if it exists
if(is_array($this->parametersRegex) && isset($this->parametersRegex[$parameter])) {
$parameterRegex = $this->parametersRegex[$parameter];
}
$routeLength = strlen($route);
for($i = 0; $i < $routeLength; $i++) {
$character = $route[$i];
// Skip "/" if we are at the end of a parameter
if($lastCharacter === '}' && $character === '/') {
$lastCharacter = $character;
continue;
}
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
if($lastCharacter === '?') {
$parameter = substr($parameter, 0, strlen($parameter)-1);
$regex .= '(?:(?:\/{0,1}(?P<'.$parameter.'>'.$parameterRegex.')){0,1}\\/{0,1})';
$required = false;
} else {
$regex .= '(?:\\/{0,1}(?P<' . $parameter . '>'. $parameterRegex .')\\/{0,1})';
}
$parameterNames[] = array('name' => $parameter, 'required' => $required);
$parameter = '';
$isParameter = false;
} elseif($isParameter) {
$parameter .= $character;
} elseif($character === '/') {
$regex .= '\\' . $character;
} else {
$regex .= str_replace('.', '\\.', $character);
}
$lastCharacter = $character;
}
$parameterValues = array();
if(preg_match('/^'.$regex.'$/is', $url, $parameterValues)) {
$parameters = array();
$max = count($parameterNames);
if($max) {
for($i = 0; $i < $max; $i++) {
$name = $parameterNames[$i];
$parameterValue = (isset($parameterValues[$name['name']]) && !empty($parameterValues[$name['name']])) ? $parameterValues[$name['name']] : null;
if($name['required'] && $parameterValue === null) {
throw new RouterException('Missing required parameter ' . $name['name'], 404);
}
$parameters[$name['name']] = $parameterValue;
}
}
return $parameters;
}
return null;
}
public function loadMiddleware(Request $request) {
if($this->getMiddleware()) {
if(is_array($this->getMiddleware())) {
foreach($this->getMiddleware() as $middleware) {
$middleware = $this->loadClass($middleware);
if (!($middleware instanceof IMiddleware)) {
throw new RouterException($middleware . ' must be instance of Middleware');
}
/* @var $class IMiddleware */
$middleware->handle($request);
}
} else {
$middleware = $this->loadClass($this->getMiddleware());
if (!($middleware instanceof IMiddleware)) {
throw new RouterException($this->getMiddleware() . ' must be instance of Middleware');
}
/* @var $class IMiddleware */
$middleware->handle($request);
}
}
}
public function renderRoute(Request $request) {
if(is_object($this->getCallback()) && 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 RouterException(sprintf('Method %s does not exist in class %s', $method, $className), 404);
}
call_user_func_array(array($class, $method), $this->getParameters());
return $class;
}
return null;
}
/**
* Set allowed request methods
*
* @param array $methods
* @return self $this
*/
public function setRequestMethods(array $methods) {
$this->settings['requestMethods'] = $methods;
return $this;
}
/**
* Get allowed requeset methods
*
* @return array
*/
public function getRequestMethods() {
if(!isset($this->settings['requestMethods']) || isset($this->settings['requestMethods']) && !is_array($this->settings['requestMethods'])) {
$value = isset($this->settings['requestMethods']) ? $this->settings['requestMethods'] : null;
return array($value);
}
return $this->settings['requestMethods'];
}
public function getGroup() {
return $this->group;
}
public function setGroup($group) {
$this->group = $group;
return $this;
}
abstract function matchRoute(Request $request);
abstract class RouterEntry
{
const REQUEST_TYPE_GET = 'get';
const REQUEST_TYPE_POST = 'post';
const REQUEST_TYPE_PUT = 'put';
const REQUEST_TYPE_PATCH = 'patch';
const REQUEST_TYPE_OPTIONS = 'options';
const REQUEST_TYPE_DELETE = 'delete';
public static $requestTypes = [
self::REQUEST_TYPE_GET,
self::REQUEST_TYPE_POST,
self::REQUEST_TYPE_PUT,
self::REQUEST_TYPE_PATCH,
self::REQUEST_TYPE_OPTIONS,
self::REQUEST_TYPE_DELETE,
];
protected $parent;
protected $callback;
protected $namespace;
protected $defaultNamespace;
protected $regex;
protected $requestMethods = array();
protected $where = array();
protected $parameters = array();
protected $middlewares = array();
protected function loadClass($name)
{
if (!class_exists($name)) {
throw new RouterException(sprintf('Class %s does not exist', $name));
}
return new $name();
}
protected function parseParameters($route, $url, $parameterRegex = '[\w]+')
{
$parameterNames = array();
$regex = '';
$lastCharacter = '';
$isParameter = false;
$parameter = '';
$routeLength = strlen($route);
for ($i = 0; $i < $routeLength; $i++) {
$character = $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 = array();
if (preg_match('/^' . $regex . '\/?$/is', $url, $parameterValues)) {
$parameters = array();
foreach ($parameterNames as $name) {
$parameterValue = isset($parameterValues[$name['name']]) ? $parameterValues[$name['name']] : null;
if ($name['required'] && $parameterValue === null) {
throw new RouterException('Missing required parameter ' . $name['name'], 404);
}
if ($name['required'] === false && $parameterValue === null) {
continue;
}
$parameters[$name['name']] = $parameterValue;
}
return $parameters;
}
return null;
}
public function loadMiddleware(Request $request, RouterEntry &$route)
{
if (count($this->getMiddlewares()) > 0) {
foreach ($this->getMiddlewares() as $middleware) {
$middleware = $this->loadClass($middleware);
if (!($middleware instanceof IMiddleware)) {
throw new RouterException($middleware . ' must be instance of Middleware');
}
/* @var $class IMiddleware */
$middleware->handle($request, $route);
}
}
}
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 RouterException(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(array($class, $method), $parameters);
return $class;
}
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
* when using translations etc.
*
* @return string
*/
public function getIdentifier()
{
if (strpos($this->callback, '@') !== false) {
return $this->callback;
}
return 'function_' . md5($this->callback);
}
/**
* Set allowed request methods
*
* @param array $methods
* @return static $this
*/
public function setRequestMethods(array $methods)
{
$this->requestMethods = $methods;
return $this;
}
/**
* Get allowed request methods
* @return array
*/
public function getRequestMethods()
{
return $this->requestMethods;
}
/**
* @return RouterEntry
*/
public function getParent()
{
return $this->parent;
}
/**
* Set parent route
*
* @param RouterEntry $parent
* @return static $this
*/
public function setParent(RouterEntry $parent)
{
$this->parent = $parent;
return $this;
}
/**
* @param string $callback
* @return static
*/
public function setCallback($callback)
{
$this->callback = $callback;
return $this;
}
/**
* @return mixed
*/
public function getCallback()
{
return $this->callback;
}
public function getMethod()
{
if (strpos($this->callback, '@') !== false) {
$tmp = explode('@', $this->callback);
return $tmp[1];
}
return null;
}
public function getClass()
{
if (strpos($this->callback, '@') !== false) {
$tmp = explode('@', $this->callback);
return $tmp[0];
}
return null;
}
public function setMethod($method)
{
$this->callback = sprintf('%s@%s', $this->getClass(), $method);
return $this;
}
public function setClass($class)
{
$this->callback = sprintf('%s@%s', $class, $this->getMethod());
return $this;
}
/**
* @param string $middleware
* @return static
*/
public function setMiddleware($middleware)
{
$this->middlewares[] = $middleware;
return $this;
}
public function setMiddlewares(array $middlewares)
{
$this->middlewares = $middlewares;
return $this;
}
/**
* @param string $namespace
* @return static $this
*/
public function setNamespace($namespace)
{
$this->namespace = $namespace;
return $this;
}
/**
* @param string $namespace
* @return static $this
*/
public function setDefaultNamespace($namespace) {
$this->defaultNamespace = $namespace;
return $this;
}
public function getDefaultNamespace() {
return $this->defaultNamespace;
}
/**
* @return string|array
*/
public function getMiddlewares()
{
return $this->middlewares;
}
/**
* @return string
*/
public function getNamespace()
{
return ($this->namespace === null) ? $this->defaultNamespace : $this->namespace;
}
/**
* @return array
*/
public function getParameters()
{
return $this->parameters;
}
/**
* @param mixed $parameters
* @return static
*/
public function setParameters($parameters)
{
$this->parameters = $parameters;
return $this;
}
/**
* Add regular expression parameter match
*
* @param array $options
* @return static
*/
public function where(array $options)
{
$this->where = $options;
return $this;
}
/**
* Add regular expression match for url
*
* @param string $regex
* @return static
*/
public function match($regex)
{
$this->regex = $regex;
return $this;
}
/**
* Export route settings to array so they can be merged with another route.
*
* @return array
*/
public function toArray()
{
$values = array();
if ($this->namespace !== null) {
$values['namespace'] = $this->namespace;
}
if (count($this->middlewares) > 0) {
$values['middleware'] = $this->middlewares;
}
if (count($this->where) > 0) {
$values['where'] = $this->where;
}
if (count($this->requestMethods) > 0) {
$values['method'] = $this->requestMethods;
}
if (count($this->parameters) > 0) {
$values['parameters'] = $this->parameters;
}
return $values;
}
/**
* Merge with information from another route.
*
* @param array $values
* @return static $this
*/
public function merge(array $values)
{
if (isset($values['namespace'])) {
$this->setNamespace($values['namespace']);
}
// Push middleware if multiple
if (isset($values['middleware'])) {
$this->middlewares = array_merge((array)$values['middleware'], $this->middlewares);
}
if (isset($values['method'])) {
$this->setRequestMethods((array)$values['method']);
}
if (isset($values['where'])) {
$this->where($values['where']);
}
if (isset($values['parameters'])) {
$this->setParameters($values['parameters']);
}
return $this;
}
abstract function matchRoute(Request $request);
}
@@ -1,3 +0,0 @@
<?php
namespace Pecee\SimpleRouter;
class RouterException extends \Exception { }
+81 -58
View File
@@ -1,83 +1,106 @@
<?php
namespace Pecee\SimpleRouter;
use Pecee\Http\Request;
class RouterGroup extends RouterEntry {
class RouterGroup extends RouterEntry
{
protected $prefix;
protected $domains = array();
protected $exceptionHandlers = array();
public function __construct() {
parent::__construct();
}
public function matchDomain(Request $request)
{
if (count($this->domains) > 0) {
foreach ($this->domains as $domain) {
protected function matchDomain(Request $request) {
if($this->domain !== null) {
$parameters = $this->parseParameters($domain, $request->getHost(), '.*');
if(is_array($this->domain)) {
if ($parameters !== null) {
$this->parameters = $parameters;
return true;
}
}
$max = count($this->domain);
return false;
}
for($i = 0; $i < $max; $i++) {
$domain = $this->domain[$i];
return true;
}
$parameters = $this->parseParameters($domain, $request->getHost(), '[^.]*');
public function matchRoute(Request $request)
{
// Skip if prefix doesn't match
if ($this->prefix !== null && stripos($request->getUri(), $this->prefix) === false) {
return false;
}
if($parameters !== null) {
$this->parameters = $parameters;
return true;
}
}
return $this->matchDomain($request);
}
return null;
}
public function setExceptionHandlers(array $handlers)
{
$this->exceptionHandlers = $handlers;
return $this;
}
$parameters = $this->parseParameters($this->domain, $request->getHost(), '[^.]*');
public function getExceptionHandlers()
{
return $this->exceptionHandlers;
}
if ($parameters !== null) {
$this->parameters = $parameters;
return true;
}
public function getDomains()
{
return $this->domains;
}
return null;
}
public function setDomains(array $domains)
{
$this->domains = $domains;
return $this;
}
return false;
}
/**
* @param string $prefix
* @return static
*/
public function setPrefix($prefix)
{
$this->prefix = '/' . trim($prefix, '/');
return $this;
}
public function renderRoute(Request $request) {
// Check if request method is allowed
$hasAccess = (!$this->method);
/**
* @return string
*/
public function getPrefix()
{
return $this->prefix;
}
if($this->method) {
if(is_array($this->method)) {
$hasAccess = (in_array($request->getMethod(), $this->getRequestMethods()));
} else {
$hasAccess = strtolower($this->getRequestMethods()) == strtolower($request->getMethod());
}
}
/**
* Merge with information from another route.
*
* @param array $values
* @return static
*/
public function merge(array $values)
{
if (isset($values['prefix'])) {
$this->setPrefix($values['prefix']);
}
if(!$hasAccess) {
throw new RouterException('Method not allowed');
}
if (isset($values['exceptionHandler'])) {
$this->setExceptionHandlers((array)$values['exceptionHandler']);
}
if($this->matchDomain($request) === null) {
return null;
}
if (isset($values['domain'])) {
$this->setDomains((array)$values['domain']);
}
return parent::renderRoute($request);
}
parent::merge($values);
public function matchRoute(Request $request) {
return null;
}
public function setExceptionHandler($class) {
$this->exceptionHandler = $class;
return $this;
}
public function getExceptionHandler() {
return $this->exceptionHandler;
}
return $this;
}
}
+90 -119
View File
@@ -1,152 +1,123 @@
<?php
namespace Pecee\SimpleRouter;
use Pecee\Exception\RouterException;
use Pecee\Http\Request;
class RouterResource extends RouterEntry {
class RouterResource extends LoadableRoute implements IControllerRoute
{
protected $controller;
const DEFAULT_METHOD = 'index';
public function __construct($url, $controller)
{
$this->setUrl($url);
$this->controller = $controller;
}
protected $url;
protected $controller;
protected $method;
protected $postMethod;
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]);
public function __construct($url, $controller) {
parent::__construct();
$this->url = $url;
$this->controller = $controller;
$this->postMethod = strtolower(($_SERVER['REQUEST_METHOD'] != 'GET') ? 'post' : 'get');
}
if (!method_exists($class, $method)) {
throw new RouterException(sprintf('Method %s does not exist in class %s', $method, $className), 404);
}
public function renderRoute(Request $request) {
if(is_object($this->getCallback()) && 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]);
call_user_func_array([$class, $method], $this->getParameters());
if (!method_exists($class, $method)) {
throw new RouterException(sprintf('Method %s does not exist in class %s', $method, $className), 404);
}
return $class;
}
call_user_func_array(array($class, $method), $this->getParameters());
return null;
}
return $class;
}
protected function call($method, $parameters)
{
$this->setCallback($this->controller . '@' . $method);
$this->parameters = $parameters;
return null;
}
return true;
}
protected function call($method, $parameters) {
$this->setCallback($this->controller . '@' . $method);
$this->parameters = $parameters;
return true;
}
public function matchRoute(Request $request)
{
$url = parse_url(urldecode($request->getUri()), PHP_URL_PATH);
$url = rtrim($url, '/') . '/';
public function matchRoute(Request $request) {
$url = parse_url($request->getUri());
$url = rtrim($url['path'], '/') . '/';
$route = rtrim($this->url, '/') . '/{id?}/{action?}';
if(strtolower($url) == strtolower($this->url) || stripos($url, $this->url) === 0) {
$url = rtrim($url, '/');
$parameters = $this->parseParameters($route, $url);
$strippedUrl = trim(substr($url, strlen($this->url)), '/');
$path = explode('/', $strippedUrl);
if ($parameters !== null) {
$args = $path;
$action = '';
$parameters = array_merge($this->parameters, (array)$parameters);
if(count($args)) {
$action = $args[0];
array_shift($args);
}
$action = isset($parameters['action']) ? $parameters['action'] : null;
unset($parameters['action']);
if (count($path)) {
$method = request()->getMethod();
// Delete
if($request->getMethod() === self::REQUEST_TYPE_DELETE && $this->postMethod === self::REQUEST_TYPE_POST) {
return $this->call('destroy', $args);
}
// Delete
if (isset($parameters['id']) && $method === static::REQUEST_TYPE_DELETE) {
return $this->call('destroy', $parameters);
}
// Update
if(in_array($request->getMethod(), array('put', 'patch')) && $this->postMethod === self::REQUEST_TYPE_POST) {
return $this->call('update', array_merge(array($action), $args));
}
// Update
if (isset($parameters['id']) && in_array($method, [static::REQUEST_TYPE_PATCH, static::REQUEST_TYPE_PUT])) {
return $this->call('update', $parameters);
}
// Edit
if(isset($args[0]) && strtolower($args[0]) === 'edit' && $this->postMethod === self::REQUEST_TYPE_GET) {
return $this->call('edit', array_merge(array($action), array_slice($args, 1)));
}
// Edit
if (isset($parameters['id']) && strtolower($action) === 'edit' && $method === static::REQUEST_TYPE_GET) {
return $this->call('edit', $parameters);
}
// Create
if(strtolower($action) === 'create' && $request->getMethod() === self::REQUEST_TYPE_GET) {
return $this->call('create', $args);
}
// Create
if (strtolower($action) === 'create' && $method === static::REQUEST_TYPE_GET) {
return $this->call('create', $parameters);
}
// Save
if($this->postMethod === self::REQUEST_TYPE_POST) {
return $this->call('store', $args);
}
// Save
if ($method === static::REQUEST_TYPE_POST) {
return $this->call('store', $parameters);
}
// Show
if($action && $this->postMethod === self::REQUEST_TYPE_GET) {
return $this->call('show', array_merge(array($action), $args));
}
// Show
if (isset($parameters['id']) && $method === static::REQUEST_TYPE_GET) {
return $this->call('show', $parameters);
}
// Index
return $this->call('index', $args);
}
}
// Index
return $this->call('index', $parameters);
}
return null;
}
return null;
}
/**
* @return string
*/
public function getUrl() {
return $this->url;
}
/**
* @return string
*/
public function getController()
{
return $this->controller;
}
/**
* @param string $url
*/
public function setUrl($url) {
$url = rtrim($url, '/') . '/';
$this->url = $url;
}
/**
* @param string $controller
* @return static
*/
public function setController($controller)
{
$this->controller = $controller;
/**
* @return string
*/
public function getController() {
return $this->controller;
}
/**
* @param string $controller
*/
public function setController($controller) {
$this->controller = $controller;
}
/**
* @return string
*/
public function getMethod() {
return $this->method;
}
/**
* @param string $method
*/
public function setMethod($method) {
$this->method = $method;
}
return $this;
}
}
+29 -101
View File
@@ -1,114 +1,42 @@
<?php
namespace Pecee\SimpleRouter;
use Pecee\Http\Request;
class RouterRoute extends RouterEntry {
class RouterRoute extends LoadableRoute
{
public function __construct($url, $callback)
{
$this->setUrl($url);
$this->setCallback($callback);
}
const PARAMETERS_REGEX_MATCH = '{([A-Za-z\-\_]*?)\?{0,1}}';
public function matchRoute(Request $request)
{
$url = parse_url(urldecode($request->getUri()), PHP_URL_PATH);
$url = rtrim($url, '/') . '/';
protected $url;
// Match on custom defined regular expression
if ($this->regex !== null) {
$parameters = array();
if (preg_match('/(' . $this->regex . ')/is', $request->getHost() . $url, $parameters)) {
$this->parameters = (array)$parameters[0];
return true;
}
return null;
}
public function __construct($url, $callback) {
parent::__construct();
$this->setUrl($url);
$this->setCallback($callback);
// Make regular expression based on route
$route = rtrim($this->url, '/') . '/';
$this->settings['aliases'] = array();
}
$parameters = $this->parseParameters($route, $url);
public function matchRoute(Request $request) {
if ($parameters !== null) {
$this->parameters = array_merge($this->parameters, $parameters);
return true;
}
$url = parse_url($request->getUri());
$url = rtrim($url['path'], '/') . '/';
// Match on custom defined regular expression
if($this->regexMatch) {
$parameters = array();
if(preg_match('/('.$this->regexMatch.')/is', $request->getHost() . $url, $parameters)) {
$this->parameters = (!is_array($parameters[0]) ? array($parameters[0]) : $parameters[0]);
return true;
}
return null;
}
// Make regular expression based on route
$route = rtrim($this->url, '/') . '/';
$parameters = $this->parseParameters($route, $url);
if($parameters !== null) {
if(is_array($this->parameters)) {
$this->parameters = array_merge($this->parameters, $parameters);
} else {
$this->parameters = $parameters;
}
return true;
}
return null;
}
/**
* @return string
*/
public function getUrl() {
return $this->url;
}
/**
* @param string $url
* @return self
*/
public function setUrl($url) {
$parameters = array();
$matches = array();
if(preg_match_all('/'.self::PARAMETERS_REGEX_MATCH.'/is', $url, $matches)) {
$parameters = $matches[1];
}
if(count($parameters)) {
$tmp = array();
foreach($parameters as $param) {
$tmp[$param] = '';
}
$this->parameters = $tmp;
}
$this->url = $url;
return $this;
}
/**
* Get alias for the url which can be used when getting the url route.
* @return string
*/
public function getAlias(){
return $this->alias;
}
/**
* Set the url alias for easier getting the url route.
* @param string $alias
* @return self
*/
public function setAlias($alias){
$this->alias = $alias;
return $this;
}
public function setSettings($settings) {
// Change as to alias
if(isset($settings{'as'})) {
$this->setAlias($settings['as']);
}
return parent::setSettings($settings);
}
return null;
}
}
+302 -94
View File
@@ -1,135 +1,343 @@
<?php
/**
* ---------------------------
* Router helper class
* ---------------------------
* This class is added so calls can be made statically like Router::get() making the code look more pretty.
*/
namespace Pecee\SimpleRouter;
use Pecee\Exception\RouterException;
use Pecee\Http\Middleware\BaseCsrfVerifier;
class SimpleRouter {
class SimpleRouter
{
protected static $defaultNamespace;
/**
* Start/route request
* @param null $defaultNamespace
* @throws RouterException
*/
public static function start($defaultNamespace = null) {
$router = RouterBase::getInstance();
$router->setDefaultNamespace($defaultNamespace);
$router->routeRequest();
}
/**
* Start/route request
*
* @throws \Pecee\Exception\RouterException
*/
public static function start()
{
static::router()->routeRequest();
}
/**
* Set base csrf verifier
* @param BaseCsrfVerifier $baseCsrfVerifier
*/
public static function csrfVerifier(BaseCsrfVerifier $baseCsrfVerifier) {
RouterBase::getInstance()->setBaseCsrfVerifier($baseCsrfVerifier);
}
/**
* Set default namespace which will be prepended to all routes.
*
* @param string $defaultNamespace
*/
public static function setDefaultNamespace($defaultNamespace)
{
static::$defaultNamespace = $defaultNamespace;
}
public static function get($url, $callback, array $settings = null) {
$route = new RouterRoute($url, $callback);
$route->addSettings($settings);
$route->setRequestMethods(array(RouterRoute::REQUEST_TYPE_GET));
/**
* Base CSRF verifier
*
* @param BaseCsrfVerifier $baseCsrfVerifier
*/
public static function csrfVerifier(BaseCsrfVerifier $baseCsrfVerifier)
{
static::router()->setCsrfVerifier($baseCsrfVerifier);
}
$router = RouterBase::getInstance();
$router->addRoute($route);
/**
* Boot managers allows you to alter the routes before the routing occurs.
* Perfect if you want to load pretty-urls from a file or database.
*
* @param RouterBootManager $bootManager
*/
public static function addBootManager(RouterBootManager $bootManager)
{
static::router()->addBootManager($bootManager);
}
return $route;
}
/**
* Route the given url to your callback on GET request method.
*
* @param string $url
* @param string|\Closure $callback
* @param array|null $settings
* @return RouterRoute
*/
public static function get($url, $callback, array $settings = null)
{
return static::match(['get'], $url, $callback, $settings);
}
public static function post($url, $callback, array $settings = null) {
$route = new RouterRoute($url, $callback);
$route->addSettings($settings);
$route->setRequestMethods(array(RouterRoute::REQUEST_TYPE_POST));
/**
* Route the given url to your callback on POST request method.
*
* @param string $url
* @param string|\Closure $callback
* @param array|null $settings
* @return RouterRoute
*/
public static function post($url, $callback, array $settings = null)
{
return static::match(['post'], $url, $callback, $settings);
}
$router = RouterBase::getInstance();
$router->addRoute($route);
/**
* Route the given url to your callback on PUT request method.
*
* @param string $url
* @param string|\Closure $callback
* @param array|null $settings
* @return RouterRoute
*/
public static function put($url, $callback, array $settings = null)
{
return static::match(['put'], $url, $callback, $settings);
}
return $route;
}
/**
* Route the given url to your callback on PATCH request method.
*
* @param string $url
* @param string|\Closure $callback
* @param array|null $settings
* @return RouterRoute
*/
public static function patch($url, $callback, array $settings = null)
{
return static::match(['patch'], $url, $callback, $settings);
}
public static function put($url, $callback, array $settings = null) {
$route = new RouterRoute($url, $callback);
$route->addSettings($settings);
$route->setRequestMethods(array(RouterRoute::REQUEST_TYPE_PUT, RouterRoute::REQUEST_TYPE_PATCH));
/**
* Route the given url to your callback on OPTIONS request method.
*
* @param string $url
* @param string|\Closure $callback
* @param array|null $settings
* @return RouterRoute
*/
public static function options($url, $callback, array $settings = null)
{
return static::match(['options'], $url, $callback, $settings);
}
$router = RouterBase::getInstance();
$router->addRoute($route);
/**
* Route the given url to your callback on DELETE request method.
*
* @param string $url
* @param string|\Closure $callback
* @param array|null $settings
* @return RouterRoute
*/
public static function delete($url, $callback, array $settings = null)
{
return static::match(['delete'], $url, $callback, $settings);
}
return $route;
}
/**
* Groups allows for encapsulating routes with special settings.
*
* @param array $settings
* @param \Closure $callback
* @throws RouterException
* @return RouterGroup
*/
public static function group(array $settings = array(), \Closure $callback)
{
$group = new RouterGroup();
$group->setCallback($callback);
$group->merge($settings);
public static function delete($url, $callback, array $settings = null) {
$route = new RouterRoute($url, $callback);
$route->addSettings($settings);
$route->setRequestMethods(array(RouterRoute::REQUEST_TYPE_DELETE));
if (is_callable($callback) === false) {
throw new RouterException('Invalid callback provided. Only functions or methods supported');
}
$router = RouterBase::getInstance();
$router->addRoute($route);
static::router()->addRoute($group);
return $route;
}
return $group;
}
public static function group($settings = array(), $callback) {
$group = new RouterGroup();
$group->setCallback($callback);
/**
* Alias for the form method
*
* @param string $url
* @param callable $callback
* @param array|null $settings
* @see SimpleRouter::form
* @return RouterRoute
*/
public static function basic($url, $callback, array $settings = null)
{
return static::match(['get', 'post'], $url, $callback, $settings);
}
if($settings !== null && is_array($settings)) {
$group->setSettings($settings);
}
/**
* This type will route the given url to your callback on the provided request methods.
* Route the given url to your callback on POST and GET request method.
*
* @param string $url
* @param string|\Closure $callback
* @param array|null $settings
* @see SimpleRouter::form
* @return RouterRoute
*/
public static function form($url, $callback, array $settings = null)
{
return static::match(['get', 'post'], $url, $callback, $settings);
}
$router = RouterBase::getInstance();
$router->addRoute($group);
/**
* This type will route the given url to your callback on the provided request methods.
*
* @param array $requestMethods
* @param string $url
* @param string|\Closure $callback
* @param array|null $settings
* @return RouterEntry|RouterRoute
*/
public static function match(array $requestMethods, $url, $callback, array $settings = null)
{
$route = new RouterRoute($url, $callback);
$route->setRequestMethods($requestMethods);
$route = static::addDefaultNamespace($route);
return $group;
}
if ($settings !== null) {
$route->merge($settings);
}
public static function match(array $requestMethods, $url, $callback, array $settings = null) {
$route = new RouterRoute($url, $callback);
$route->setRequestMethods($requestMethods);
$route->addSettings($settings);
static::router()->addRoute($route);
$router = RouterBase::getInstance();
$router->addRoute($route);
return $route;
}
return $route;
}
/**
* This type will route the given url to your callback and allow any type of request method
*
* @param string $url
* @param string|\Closure $callback
* @param array|null $settings
* @return RouterRoute
*/
public static function all($url, $callback, array $settings = null)
{
$route = new RouterRoute($url, $callback);
public static function all($url, $callback, array $settings = null) {
$route = new RouterRoute($url, $callback);
$route->addSettings($settings);
$router = RouterBase::getInstance();
$router->addRoute($route);
$route = static::addDefaultNamespace($route);
return $route;
}
if ($settings !== null) {
$route->merge($settings);
}
public static function controller($url, $controller, array $settings = null) {
$route = new RouterController($url, $controller);
$route->addSettings($settings);
$router = RouterBase::getInstance();
$router->addRoute($route);
static::router()->addRoute($route);
return $route;
}
return $route;
}
public static function resource($url, $controller, array $settings = null) {
$route = new RouterResource($url, $controller);
$route->addSettings($settings);
$router = RouterBase::getInstance();
$router->addRoute($route);
/**
* This route will route request from the given url to the controller.
*
* @param string $url
* @param string $controller
* @param array|null $settings
* @return RouterController
*/
public static function controller($url, $controller, array $settings = null)
{
$route = new RouterController($url, $controller);
return $route;
}
$route = static::addDefaultNamespace($route);
public static function getRoute($controller = null, $parameters = null, $getParams = null) {
return RouterBase::getInstance()->getRoute($controller, $parameters, $getParams);
}
if ($settings !== null) {
$route->merge($settings);
}
static::router()->addRoute($route);
return $route;
}
/**
* This type will route all REST-supported requests to different methods in the provided controller.
*
* @param string $url
* @param string $controller
* @param array|null $settings
* @return RouterResource
*/
public static function resource($url, $controller, array $settings = null)
{
$route = new RouterResource($url, $controller);
if ($settings !== null) {
$route->merge($settings);
}
static::router()->addRoute($route);
return $route;
}
/**
* Get url by controller or alias.
*
* @param string $controller
* @param array|null $parameters
* @param array|null $getParams
* @return string
*/
public static function getRoute($controller = null, $parameters = null, $getParams = null)
{
return static::router()->getRoute($controller, $parameters, $getParams);
}
/**
* Get the request
*
* @return \Pecee\Http\Request
*/
public static function request()
{
return static::router()->getRequest();
}
/**
* Get the response object
*
* @return \Pecee\Http\Response
*/
public static function response()
{
return static::router()->getResponse();
}
/**
* Returns the router instance
*
* @return RouterBase
*/
public static function router()
{
return RouterBase::getInstance();
}
/**
* Prepends the default namespace to all new routes added.
*
* @param RouterEntry $route
* @return RouterEntry
*/
protected static function addDefaultNamespace(RouterEntry $route)
{
if (static::$defaultNamespace !== null) {
$namespace = static::$defaultNamespace;
if ($route->getNamespace() !== null) {
$namespace .= '\\' . $route->getNamespace();
}
$route->setDefaultNamespace($namespace);
}
return $route;
}
}
+25
View File
@@ -0,0 +1,25 @@
<?php
class DummyController
{
public function start()
{
echo static::class . '@' . 'start() OK';
}
public function param($params = null)
{
$params = func_get_args();
echo 'Params: ' . join(', ', $params);
}
public function notFound()
{
echo 'not found';
}
public function silent() {
}
}
+14
View File
@@ -0,0 +1,14 @@
<?php
require_once 'Exceptions/MiddlewareLoadedException.php';
use Pecee\Http\Middleware\IMiddleware;
use Pecee\Http\Request;
class DummyMiddleware implements IMiddleware
{
public function handle(Request $request, \Pecee\SimpleRouter\RouterEntry &$route = null)
{
throw new MiddlewareLoadedException('Middleware loaded!');
}
}
@@ -0,0 +1,4 @@
<?php
class MiddlewareLoadedException extends \Exception
{
}
+10
View File
@@ -0,0 +1,10 @@
<?php
class ExceptionHandler implements \Pecee\Handler\IExceptionHandler
{
public function handleError(\Pecee\Http\Request $request, \Pecee\SimpleRouter\RouterEntry &$route = null, \Exception $error)
{
throw $error;
}
}
+92
View File
@@ -0,0 +1,92 @@
<?php
require_once 'Dummy/DummyMiddleware.php';
require_once 'Dummy/DummyController.php';
use Pecee\SimpleRouter\SimpleRouter as SimpleRouter;
class GroupTest extends PHPUnit_Framework_TestCase
{
protected $result;
public function testGroupLoad()
{
$this->result = false;
SimpleRouter::group(['prefix' => '/group'], function () {
$this->result = true;
});
try {
SimpleRouter::start();
} catch (Exception $e) {
// ignore RouteNotFound exception
}
$this->assertTrue($this->result);
}
public function testNestedGroup()
{
SimpleRouter::router()->reset();
SimpleRouter::request()->setUri('/api/v1/test');
SimpleRouter::request()->setMethod('get');
SimpleRouter::group(['prefix' => '/api'], function () {
SimpleRouter::group(['prefix' => '/v1'], function () {
SimpleRouter::get('/test', 'DummyController@start');
});
});
SimpleRouter::start();
}
public function testManyRoutes()
{
SimpleRouter::router()->reset();
SimpleRouter::request()->setUri('/my/match');
SimpleRouter::request()->setMethod('get');
SimpleRouter::group(['prefix' => '/api'], function () {
SimpleRouter::group(['prefix' => '/v1'], function () {
SimpleRouter::get('/test', 'DummyController@start');
});
});
SimpleRouter::get('/my/match', 'DummyController@start');
SimpleRouter::group(['prefix' => '/service'], function () {
SimpleRouter::group(['prefix' => '/v1'], function () {
SimpleRouter::get('/no-match', 'DummyController@start');
});
});
SimpleRouter::start();
}
public function testUrls()
{
SimpleRouter::router()->reset();
SimpleRouter::request()->setUri('/my/fancy/url/1');
SimpleRouter::request()->setMethod('get');
SimpleRouter::get('/my/fancy/url/1', 'DummyController@start', ['as' => 'fancy1']);
SimpleRouter::get('/my/fancy/url/2', 'DummyController@start')->setAlias('fancy2');
SimpleRouter::start();
$this->assertTrue((SimpleRouter::getRoute('fancy1') === '/my/fancy/url/1/'));
$this->assertTrue((SimpleRouter::getRoute('fancy2') === '/my/fancy/url/2/'));
}
}
+32
View File
@@ -0,0 +1,32 @@
<?php
require_once 'Dummy/DummyMiddleware.php';
require_once 'Dummy/DummyController.php';
require_once 'Dummy/Handler/ExceptionHandler.php';
use Pecee\SimpleRouter\SimpleRouter as SimpleRouter;
class MiddlewareTest extends PHPUnit_Framework_TestCase
{
public function testMiddlewareFound()
{
SimpleRouter::router()->reset();
SimpleRouter::request()->setMethod('get');
SimpleRouter::request()->setUri('/my/test/url');
SimpleRouter::group(['exceptionHandler' => 'ExceptionHandler'], function () {
SimpleRouter::get('/my/test/url', 'DummyController@start', ['middleware' => 'DummyMiddleware']);
});
$found = false;
try {
SimpleRouter::start();
} catch (\Exception $e) {
$found = ($e instanceof MiddlewareLoadedException);
}
$this->assertTrue($found);
}
}
+140
View File
@@ -0,0 +1,140 @@
<?php
require_once 'Dummy/DummyMiddleware.php';
require_once 'Dummy/DummyController.php';
require_once 'Dummy/Handler/ExceptionHandler.php';
use Pecee\SimpleRouter\SimpleRouter as SimpleRouter;
class RouterRouteTest extends PHPUnit_Framework_TestCase
{
protected $result = false;
public function testNotFound()
{
SimpleRouter::router()->reset();
SimpleRouter::request()->setMethod('get');
SimpleRouter::request()->setUri('/test-param1-param2');
SimpleRouter::group(['exceptionHandler' => 'ExceptionHandler'], function () {
SimpleRouter::get('/non-existing-path', 'DummyController@start');
});
$found = false;
try {
SimpleRouter::start();
} catch (\Exception $e) {
$found = ($e instanceof \Pecee\Exception\RouterException && $e->getCode() == 404);
}
$this->assertTrue($found);
}
public function testGet()
{
SimpleRouter::router()->reset();
SimpleRouter::request()->setUri('/my/test/url');
SimpleRouter::request()->setMethod('get');
SimpleRouter::get('/my/test/url', 'DummyController@start');
SimpleRouter::start();
}
public function testPost()
{
SimpleRouter::router()->reset();
SimpleRouter::request()->setUri('/my/test/url');
SimpleRouter::request()->setMethod('post');
SimpleRouter::post('/my/test/url', 'DummyController@start');
SimpleRouter::start();
}
public function testPut()
{
SimpleRouter::router()->reset();
SimpleRouter::request()->setUri('/my/test/url');
SimpleRouter::request()->setMethod('put');
SimpleRouter::put('/my/test/url', 'DummyController@start');
SimpleRouter::start();
}
public function testDelete()
{
SimpleRouter::router()->reset();
SimpleRouter::request()->setUri('/my/test/url');
SimpleRouter::request()->setMethod('delete');
SimpleRouter::delete('/my/test/url', 'DummyController@start');
SimpleRouter::start();
}
public function testMethodNotAllowed()
{
SimpleRouter::router()->reset();
SimpleRouter::request()->setUri('/my/test/url');
SimpleRouter::request()->setMethod('post');
SimpleRouter::get('/my/test/url', 'DummyController@start');
try {
SimpleRouter::start();
} catch (\Exception $e) {
$this->assertEquals(403, $e->getCode());
}
}
public function testSimpleParam()
{
SimpleRouter::router()->reset();
SimpleRouter::request()->setMethod('get');
SimpleRouter::request()->setUri('/test-param1');
SimpleRouter::get('/test-{param1}', 'DummyController@param');
SimpleRouter::start();
}
public function testMultiParam()
{
SimpleRouter::router()->reset();
SimpleRouter::request()->setMethod('get');
SimpleRouter::request()->setUri('/test-param1-param2');
SimpleRouter::get('/test-{param1}-{param2}', 'DummyController@param');
SimpleRouter::start();
}
public function testPathParamRegex()
{
SimpleRouter::router()->reset();
SimpleRouter::request()->setMethod('get');
SimpleRouter::request()->setUri('/test/path/123123');
SimpleRouter::get('/test/path/{myParam}', 'DummyController@param', ['where' => ['myParam' => '([0-9]+)']]);
SimpleRouter::start();
}
public function testDomainRoute()
{
SimpleRouter::router()->reset();
SimpleRouter::request()->setMethod('get');
SimpleRouter::request()->setUri('/test');
SimpleRouter::request()->setHost('hello.world.com');
$this->result = false;
SimpleRouter::group(['domain' => '{subdomain}.world.com'], function () {
SimpleRouter::get('/test', function ($subdomain = null) {
$this->result = ($subdomain === 'hello');
});
});
SimpleRouter::start();
$this->assertTrue($this->result);
}
}
+83
View File
@@ -0,0 +1,83 @@
<?php
require_once 'Dummy/DummyMiddleware.php';
require_once 'Dummy/DummyController.php';
require_once 'Dummy/Handler/ExceptionHandler.php';
use Pecee\SimpleRouter\SimpleRouter as SimpleRouter;
class RouterUrlTest extends PHPUnit_Framework_TestCase
{
protected $result = false;
protected function getUrl($controller = null, $parameters = null, $getParams = null) {
return SimpleRouter::getRoute($controller, $parameters, $getParams);
}
public function testUrls()
{
SimpleRouter::router()->reset();
SimpleRouter::request()->setMethod('get');
SimpleRouter::request()->setUri('/');
// Match normal route on alias
SimpleRouter::get('/', 'DummyController@silent', ['as' => 'home']);
SimpleRouter::group(['prefix' => '/admin'], function() {
// Match route with prefix on alias
SimpleRouter::get('/{id?}', 'DummyController@start', ['as' => 'admin.home']);
// Match controller with prefix and alias
SimpleRouter::controller('/users', 'DummyController', ['as' => 'admin.users']);
// Match controller with prefix and NO alias
SimpleRouter::controller('/pages', 'DummyController');
});
// Match controller with no prefix and no alias
SimpleRouter::controller('/cats', 'CatsController');
// Pretend to load page
SimpleRouter::start();
// Should match /
$this->assertEquals($this->getUrl('home'), '/');
// Should match /admin/
$this->assertEquals($this->getUrl('DummyController@start'), '/admin/');
// Should match /admin/
$this->assertEquals($this->getUrl('admin.home'), '/admin/');
// Should match /admin/2/
$this->assertEquals($this->getUrl('admin.home', ['id' => 2]), '/admin/2/');
// Should match /admin/users/
$this->assertEquals($this->getUrl('admin.users'), '/admin/users/');
// Should match /admin/users/home/
$this->assertEquals($this->getUrl('admin.users@home'), '/admin/users/home/');
// Should match /cats/
$this->assertEquals($this->getUrl('CatsController'), '/cats/');
// Should match /cats/view/
$this->assertEquals($this->getUrl('CatsController', 'view'), '/cats/view/');
// Should match /cats/view/
$this->assertEquals($this->getUrl('CatsController', ['view']), '/cats/view/');
// Should match /cats/view/666
$this->assertEquals($this->getUrl('CatsController@view', ['666']), '/cats/view/666/');
// Should match /funny/man/
$this->assertEquals($this->getUrl('/funny/man'), '/funny/man/');
// Should match /?jackdaniels=true
$this->assertEquals($this->getUrl('home', null, ['jackdaniels' => 'true']), '/?jackdaniels=true');
}
}