├── .gitignore ├── .travis.yml ├── LICENSE ├── composer.json ├── composer.lock ├── phpunit.xml.dist ├── readme.md ├── src └── Routing │ ├── Dispatcher │ ├── ClosureDispatcher.php │ ├── ControllerDispatcher.php │ ├── DispatcherInterface.php │ └── TemplateDispatcher.php │ ├── Matcher │ ├── ArrayMatcher.php │ ├── MatcherInterface.php │ └── UriMatcher.php │ ├── Middleware.php │ ├── MiddlewareInterface.php │ ├── Response.php │ ├── ResponseInterface.php │ ├── Route.php │ ├── RouteCollection.php │ └── Router.php └── tests ├── app ├── Block1 │ ├── Namespace1Controller.php │ ├── Normal1Controller.php │ ├── Views │ │ ├── Smart │ │ │ └── index1.html │ │ ├── contact.php │ │ ├── index.html │ │ ├── log.php │ │ └── user.html │ └── routes.php ├── Block2 │ ├── Controllers │ │ ├── Namespace2Controller.php │ │ └── Normal2Controller.php │ ├── Smart │ │ └── index2.html │ ├── index.html │ ├── log.php │ ├── routes.php │ └── user.html ├── Config │ ├── middleware.inc.php │ └── routes.php ├── Controllers │ ├── AppController.php │ ├── NamespaceController.php │ └── NormalController.php └── Views │ ├── Smart │ └── index.html │ ├── index.html │ └── user.html ├── bootstrap.php └── src └── Routing ├── MiddlewareTest.php ├── RouteCollectionTest.php └── RouterTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | /Block1 3 | Autoload.php 4 | .htaccess 5 | /.git 6 | index.php 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | php: 3 | - "7.1" 4 | - "7.0" 5 | - "5.6" 6 | 7 | before_script: 8 | - composer self-update 9 | - composer install 10 | 11 | script: 12 | - vendor/bin/phpunit -c phpunit.xml.dist 13 | 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 jetfirephp 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jetfirephp/routing", 3 | "type": "library", 4 | "description": "JetFire - Routing", 5 | "keywords": ["Rounting", "Dispatcher", "Micro MVC"], 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Sumugan Sinnarasa", 10 | "email": "mugan27@gmail.com", 11 | "role": "Developer" 12 | } 13 | ], 14 | "require": { 15 | "php": ">=5.6.0" 16 | }, 17 | "require-dev": { 18 | "phpunit/phpunit": "5.7.*", 19 | "symfony/yaml": "v2.8.2" 20 | }, 21 | "suggest": { 22 | "jetfire/autoload": "For loading your class" 23 | }, 24 | "autoload": { 25 | "psr-4":{ 26 | "JetFire\\Routing\\": "src/Routing/" 27 | } 28 | }, 29 | "autoload-dev": { 30 | "psr-4": { 31 | "JetFire\\Routing\\Test\\": "tests/src/Routing/", 32 | "JetFire\\Routing\\App\\": "tests/app/" 33 | } 34 | }, 35 | "minimum-stability": "dev" 36 | } 37 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", 5 | "This file is @generated automatically" 6 | ], 7 | "hash": "458848d0bc194d11df02bfede2b4c79b", 8 | "content-hash": "154fed92e2d6c624e53ea1027c8d88ba", 9 | "packages": [], 10 | "packages-dev": [ 11 | { 12 | "name": "doctrine/instantiator", 13 | "version": "dev-master", 14 | "source": { 15 | "type": "git", 16 | "url": "https://github.com/doctrine/instantiator.git", 17 | "reference": "5acd2bd8c2b600ad5cc4c9180ebf0a930604d6a5" 18 | }, 19 | "dist": { 20 | "type": "zip", 21 | "url": "https://api.github.com/repos/doctrine/instantiator/zipball/5acd2bd8c2b600ad5cc4c9180ebf0a930604d6a5", 22 | "reference": "5acd2bd8c2b600ad5cc4c9180ebf0a930604d6a5", 23 | "shasum": "" 24 | }, 25 | "require": { 26 | "php": ">=5.3,<8.0-DEV" 27 | }, 28 | "require-dev": { 29 | "athletic/athletic": "~0.1.8", 30 | "ext-pdo": "*", 31 | "ext-phar": "*", 32 | "phpunit/phpunit": "~4.0", 33 | "squizlabs/php_codesniffer": "~2.0" 34 | }, 35 | "type": "library", 36 | "extra": { 37 | "branch-alias": { 38 | "dev-master": "1.0.x-dev" 39 | } 40 | }, 41 | "autoload": { 42 | "psr-4": { 43 | "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" 44 | } 45 | }, 46 | "notification-url": "https://packagist.org/downloads/", 47 | "license": [ 48 | "MIT" 49 | ], 50 | "authors": [ 51 | { 52 | "name": "Marco Pivetta", 53 | "email": "ocramius@gmail.com", 54 | "homepage": "http://ocramius.github.com/" 55 | } 56 | ], 57 | "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", 58 | "homepage": "https://github.com/doctrine/instantiator", 59 | "keywords": [ 60 | "constructor", 61 | "instantiate" 62 | ], 63 | "time": "2017-02-16 16:15:51" 64 | }, 65 | { 66 | "name": "myclabs/deep-copy", 67 | "version": "1.x-dev", 68 | "source": { 69 | "type": "git", 70 | "url": "https://github.com/myclabs/DeepCopy.git", 71 | "reference": "8e6e04167378abf1ddb4d3522d8755c5fd90d102" 72 | }, 73 | "dist": { 74 | "type": "zip", 75 | "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/8e6e04167378abf1ddb4d3522d8755c5fd90d102", 76 | "reference": "8e6e04167378abf1ddb4d3522d8755c5fd90d102", 77 | "shasum": "" 78 | }, 79 | "require": { 80 | "php": ">=5.4.0" 81 | }, 82 | "require-dev": { 83 | "doctrine/collections": "1.*", 84 | "phpunit/phpunit": "~4.1" 85 | }, 86 | "type": "library", 87 | "autoload": { 88 | "psr-4": { 89 | "DeepCopy\\": "src/DeepCopy/" 90 | } 91 | }, 92 | "notification-url": "https://packagist.org/downloads/", 93 | "license": [ 94 | "MIT" 95 | ], 96 | "description": "Create deep copies (clones) of your objects", 97 | "homepage": "https://github.com/myclabs/DeepCopy", 98 | "keywords": [ 99 | "clone", 100 | "copy", 101 | "duplicate", 102 | "object", 103 | "object graph" 104 | ], 105 | "time": "2017-04-12 18:52:22" 106 | }, 107 | { 108 | "name": "phpdocumentor/reflection-common", 109 | "version": "dev-master", 110 | "source": { 111 | "type": "git", 112 | "url": "https://github.com/phpDocumentor/ReflectionCommon.git", 113 | "reference": "a046af61c36e9162372f205de091a1cab7340f1c" 114 | }, 115 | "dist": { 116 | "type": "zip", 117 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/a046af61c36e9162372f205de091a1cab7340f1c", 118 | "reference": "a046af61c36e9162372f205de091a1cab7340f1c", 119 | "shasum": "" 120 | }, 121 | "require": { 122 | "php": ">=5.5" 123 | }, 124 | "require-dev": { 125 | "phpunit/phpunit": "^4.6" 126 | }, 127 | "type": "library", 128 | "extra": { 129 | "branch-alias": { 130 | "dev-master": "1.0.x-dev" 131 | } 132 | }, 133 | "autoload": { 134 | "psr-4": { 135 | "phpDocumentor\\Reflection\\": [ 136 | "src" 137 | ] 138 | } 139 | }, 140 | "notification-url": "https://packagist.org/downloads/", 141 | "license": [ 142 | "MIT" 143 | ], 144 | "authors": [ 145 | { 146 | "name": "Jaap van Otterdijk", 147 | "email": "opensource@ijaap.nl" 148 | } 149 | ], 150 | "description": "Common reflection classes used by phpdocumentor to reflect the code structure", 151 | "homepage": "http://www.phpdoc.org", 152 | "keywords": [ 153 | "FQSEN", 154 | "phpDocumentor", 155 | "phpdoc", 156 | "reflection", 157 | "static analysis" 158 | ], 159 | "time": "2017-04-30 11:58:12" 160 | }, 161 | { 162 | "name": "phpdocumentor/reflection-docblock", 163 | "version": "3.2.0", 164 | "source": { 165 | "type": "git", 166 | "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", 167 | "reference": "46f7e8bb075036c92695b15a1ddb6971c751e585" 168 | }, 169 | "dist": { 170 | "type": "zip", 171 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/46f7e8bb075036c92695b15a1ddb6971c751e585", 172 | "reference": "46f7e8bb075036c92695b15a1ddb6971c751e585", 173 | "shasum": "" 174 | }, 175 | "require": { 176 | "php": ">=5.5", 177 | "phpdocumentor/reflection-common": "^1.0@dev", 178 | "phpdocumentor/type-resolver": "^0.4.0", 179 | "webmozart/assert": "^1.0" 180 | }, 181 | "require-dev": { 182 | "mockery/mockery": "^0.9.4", 183 | "phpunit/phpunit": "^4.4" 184 | }, 185 | "type": "library", 186 | "autoload": { 187 | "psr-4": { 188 | "phpDocumentor\\Reflection\\": [ 189 | "src/" 190 | ] 191 | } 192 | }, 193 | "notification-url": "https://packagist.org/downloads/", 194 | "license": [ 195 | "MIT" 196 | ], 197 | "authors": [ 198 | { 199 | "name": "Mike van Riel", 200 | "email": "me@mikevanriel.com" 201 | } 202 | ], 203 | "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", 204 | "time": "2017-07-15 11:38:20" 205 | }, 206 | { 207 | "name": "phpdocumentor/type-resolver", 208 | "version": "0.4.0", 209 | "source": { 210 | "type": "git", 211 | "url": "https://github.com/phpDocumentor/TypeResolver.git", 212 | "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7" 213 | }, 214 | "dist": { 215 | "type": "zip", 216 | "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7", 217 | "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7", 218 | "shasum": "" 219 | }, 220 | "require": { 221 | "php": "^5.5 || ^7.0", 222 | "phpdocumentor/reflection-common": "^1.0" 223 | }, 224 | "require-dev": { 225 | "mockery/mockery": "^0.9.4", 226 | "phpunit/phpunit": "^5.2||^4.8.24" 227 | }, 228 | "type": "library", 229 | "extra": { 230 | "branch-alias": { 231 | "dev-master": "1.0.x-dev" 232 | } 233 | }, 234 | "autoload": { 235 | "psr-4": { 236 | "phpDocumentor\\Reflection\\": [ 237 | "src/" 238 | ] 239 | } 240 | }, 241 | "notification-url": "https://packagist.org/downloads/", 242 | "license": [ 243 | "MIT" 244 | ], 245 | "authors": [ 246 | { 247 | "name": "Mike van Riel", 248 | "email": "me@mikevanriel.com" 249 | } 250 | ], 251 | "time": "2017-07-14 14:27:02" 252 | }, 253 | { 254 | "name": "phpspec/prophecy", 255 | "version": "dev-master", 256 | "source": { 257 | "type": "git", 258 | "url": "https://github.com/phpspec/prophecy.git", 259 | "reference": "79db5dd907ee977039f77ac8471a739497463429" 260 | }, 261 | "dist": { 262 | "type": "zip", 263 | "url": "https://api.github.com/repos/phpspec/prophecy/zipball/79db5dd907ee977039f77ac8471a739497463429", 264 | "reference": "79db5dd907ee977039f77ac8471a739497463429", 265 | "shasum": "" 266 | }, 267 | "require": { 268 | "doctrine/instantiator": "^1.0.2", 269 | "php": "^5.3|^7.0", 270 | "phpdocumentor/reflection-docblock": "^2.0|^3.0.2", 271 | "sebastian/comparator": "^1.1|^2.0", 272 | "sebastian/recursion-context": "^1.0|^2.0|^3.0" 273 | }, 274 | "require-dev": { 275 | "phpspec/phpspec": "^2.5|^3.2", 276 | "phpunit/phpunit": "^4.8 || ^5.6.5" 277 | }, 278 | "type": "library", 279 | "extra": { 280 | "branch-alias": { 281 | "dev-master": "1.7.x-dev" 282 | } 283 | }, 284 | "autoload": { 285 | "psr-0": { 286 | "Prophecy\\": "src/" 287 | } 288 | }, 289 | "notification-url": "https://packagist.org/downloads/", 290 | "license": [ 291 | "MIT" 292 | ], 293 | "authors": [ 294 | { 295 | "name": "Konstantin Kudryashov", 296 | "email": "ever.zet@gmail.com", 297 | "homepage": "http://everzet.com" 298 | }, 299 | { 300 | "name": "Marcello Duarte", 301 | "email": "marcello.duarte@gmail.com" 302 | } 303 | ], 304 | "description": "Highly opinionated mocking framework for PHP 5.3+", 305 | "homepage": "https://github.com/phpspec/prophecy", 306 | "keywords": [ 307 | "Double", 308 | "Dummy", 309 | "fake", 310 | "mock", 311 | "spy", 312 | "stub" 313 | ], 314 | "time": "2017-05-31 16:22:32" 315 | }, 316 | { 317 | "name": "phpunit/php-code-coverage", 318 | "version": "4.0.x-dev", 319 | "source": { 320 | "type": "git", 321 | "url": "https://github.com/sebastianbergmann/php-code-coverage.git", 322 | "reference": "ef7b2f56815df854e66ceaee8ebe9393ae36a40d" 323 | }, 324 | "dist": { 325 | "type": "zip", 326 | "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ef7b2f56815df854e66ceaee8ebe9393ae36a40d", 327 | "reference": "ef7b2f56815df854e66ceaee8ebe9393ae36a40d", 328 | "shasum": "" 329 | }, 330 | "require": { 331 | "ext-dom": "*", 332 | "ext-xmlwriter": "*", 333 | "php": "^5.6 || ^7.0", 334 | "phpunit/php-file-iterator": "^1.3", 335 | "phpunit/php-text-template": "^1.2", 336 | "phpunit/php-token-stream": "^1.4.2 || ^2.0", 337 | "sebastian/code-unit-reverse-lookup": "^1.0", 338 | "sebastian/environment": "^1.3.2 || ^2.0", 339 | "sebastian/version": "^1.0 || ^2.0" 340 | }, 341 | "require-dev": { 342 | "ext-xdebug": "^2.1.4", 343 | "phpunit/phpunit": "^5.7" 344 | }, 345 | "suggest": { 346 | "ext-xdebug": "^2.5.1" 347 | }, 348 | "type": "library", 349 | "extra": { 350 | "branch-alias": { 351 | "dev-master": "4.0.x-dev" 352 | } 353 | }, 354 | "autoload": { 355 | "classmap": [ 356 | "src/" 357 | ] 358 | }, 359 | "notification-url": "https://packagist.org/downloads/", 360 | "license": [ 361 | "BSD-3-Clause" 362 | ], 363 | "authors": [ 364 | { 365 | "name": "Sebastian Bergmann", 366 | "email": "sb@sebastian-bergmann.de", 367 | "role": "lead" 368 | } 369 | ], 370 | "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", 371 | "homepage": "https://github.com/sebastianbergmann/php-code-coverage", 372 | "keywords": [ 373 | "coverage", 374 | "testing", 375 | "xunit" 376 | ], 377 | "time": "2017-04-02 07:44:40" 378 | }, 379 | { 380 | "name": "phpunit/php-file-iterator", 381 | "version": "dev-master", 382 | "source": { 383 | "type": "git", 384 | "url": "https://github.com/sebastianbergmann/php-file-iterator.git", 385 | "reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5" 386 | }, 387 | "dist": { 388 | "type": "zip", 389 | "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/3cc8f69b3028d0f96a9078e6295d86e9bf019be5", 390 | "reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5", 391 | "shasum": "" 392 | }, 393 | "require": { 394 | "php": ">=5.3.3" 395 | }, 396 | "type": "library", 397 | "extra": { 398 | "branch-alias": { 399 | "dev-master": "1.4.x-dev" 400 | } 401 | }, 402 | "autoload": { 403 | "classmap": [ 404 | "src/" 405 | ] 406 | }, 407 | "notification-url": "https://packagist.org/downloads/", 408 | "license": [ 409 | "BSD-3-Clause" 410 | ], 411 | "authors": [ 412 | { 413 | "name": "Sebastian Bergmann", 414 | "email": "sb@sebastian-bergmann.de", 415 | "role": "lead" 416 | } 417 | ], 418 | "description": "FilterIterator implementation that filters files based on a list of suffixes.", 419 | "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", 420 | "keywords": [ 421 | "filesystem", 422 | "iterator" 423 | ], 424 | "time": "2016-10-03 07:40:28" 425 | }, 426 | { 427 | "name": "phpunit/php-text-template", 428 | "version": "1.2.1", 429 | "source": { 430 | "type": "git", 431 | "url": "https://github.com/sebastianbergmann/php-text-template.git", 432 | "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" 433 | }, 434 | "dist": { 435 | "type": "zip", 436 | "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", 437 | "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", 438 | "shasum": "" 439 | }, 440 | "require": { 441 | "php": ">=5.3.3" 442 | }, 443 | "type": "library", 444 | "autoload": { 445 | "classmap": [ 446 | "src/" 447 | ] 448 | }, 449 | "notification-url": "https://packagist.org/downloads/", 450 | "license": [ 451 | "BSD-3-Clause" 452 | ], 453 | "authors": [ 454 | { 455 | "name": "Sebastian Bergmann", 456 | "email": "sebastian@phpunit.de", 457 | "role": "lead" 458 | } 459 | ], 460 | "description": "Simple template engine.", 461 | "homepage": "https://github.com/sebastianbergmann/php-text-template/", 462 | "keywords": [ 463 | "template" 464 | ], 465 | "time": "2015-06-21 13:50:34" 466 | }, 467 | { 468 | "name": "phpunit/php-timer", 469 | "version": "dev-master", 470 | "source": { 471 | "type": "git", 472 | "url": "https://github.com/sebastianbergmann/php-timer.git", 473 | "reference": "d107f347d368dd8a384601398280c7c608390ab7" 474 | }, 475 | "dist": { 476 | "type": "zip", 477 | "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/d107f347d368dd8a384601398280c7c608390ab7", 478 | "reference": "d107f347d368dd8a384601398280c7c608390ab7", 479 | "shasum": "" 480 | }, 481 | "require": { 482 | "php": "^5.3.3 || ^7.0" 483 | }, 484 | "require-dev": { 485 | "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" 486 | }, 487 | "type": "library", 488 | "extra": { 489 | "branch-alias": { 490 | "dev-master": "1.0-dev" 491 | } 492 | }, 493 | "autoload": { 494 | "classmap": [ 495 | "src/" 496 | ] 497 | }, 498 | "notification-url": "https://packagist.org/downloads/", 499 | "license": [ 500 | "BSD-3-Clause" 501 | ], 502 | "authors": [ 503 | { 504 | "name": "Sebastian Bergmann", 505 | "email": "sb@sebastian-bergmann.de", 506 | "role": "lead" 507 | } 508 | ], 509 | "description": "Utility class for timing", 510 | "homepage": "https://github.com/sebastianbergmann/php-timer/", 511 | "keywords": [ 512 | "timer" 513 | ], 514 | "time": "2017-03-07 15:42:04" 515 | }, 516 | { 517 | "name": "phpunit/php-token-stream", 518 | "version": "dev-master", 519 | "source": { 520 | "type": "git", 521 | "url": "https://github.com/sebastianbergmann/php-token-stream.git", 522 | "reference": "9ddb181faa4a3841fe131c357fd01de75cbb4da9" 523 | }, 524 | "dist": { 525 | "type": "zip", 526 | "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/9ddb181faa4a3841fe131c357fd01de75cbb4da9", 527 | "reference": "9ddb181faa4a3841fe131c357fd01de75cbb4da9", 528 | "shasum": "" 529 | }, 530 | "require": { 531 | "ext-tokenizer": "*", 532 | "php": "^5.6 || ^7.0" 533 | }, 534 | "require-dev": { 535 | "phpunit/phpunit": "^5.7 || ^6.0" 536 | }, 537 | "type": "library", 538 | "extra": { 539 | "branch-alias": { 540 | "dev-master": "2.0-dev" 541 | } 542 | }, 543 | "autoload": { 544 | "classmap": [ 545 | "src/" 546 | ] 547 | }, 548 | "notification-url": "https://packagist.org/downloads/", 549 | "license": [ 550 | "BSD-3-Clause" 551 | ], 552 | "authors": [ 553 | { 554 | "name": "Sebastian Bergmann", 555 | "email": "sebastian@phpunit.de" 556 | } 557 | ], 558 | "description": "Wrapper around PHP's tokenizer extension.", 559 | "homepage": "https://github.com/sebastianbergmann/php-token-stream/", 560 | "keywords": [ 561 | "tokenizer" 562 | ], 563 | "time": "2017-03-07 07:36:57" 564 | }, 565 | { 566 | "name": "phpunit/phpunit", 567 | "version": "5.7.x-dev", 568 | "source": { 569 | "type": "git", 570 | "url": "https://github.com/sebastianbergmann/phpunit.git", 571 | "reference": "c0c61c97be37e1c77ab61ad73448e9208c05aade" 572 | }, 573 | "dist": { 574 | "type": "zip", 575 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c0c61c97be37e1c77ab61ad73448e9208c05aade", 576 | "reference": "c0c61c97be37e1c77ab61ad73448e9208c05aade", 577 | "shasum": "" 578 | }, 579 | "require": { 580 | "ext-dom": "*", 581 | "ext-json": "*", 582 | "ext-libxml": "*", 583 | "ext-mbstring": "*", 584 | "ext-xml": "*", 585 | "myclabs/deep-copy": "~1.3", 586 | "php": "^5.6 || ^7.0", 587 | "phpspec/prophecy": "^1.6.2", 588 | "phpunit/php-code-coverage": "^4.0.4", 589 | "phpunit/php-file-iterator": "~1.4", 590 | "phpunit/php-text-template": "~1.2", 591 | "phpunit/php-timer": "^1.0.6", 592 | "phpunit/phpunit-mock-objects": "^3.2", 593 | "sebastian/comparator": "^1.2.4", 594 | "sebastian/diff": "^1.4.3", 595 | "sebastian/environment": "^1.3.4 || ^2.0", 596 | "sebastian/exporter": "~2.0", 597 | "sebastian/global-state": "^1.1", 598 | "sebastian/object-enumerator": "~2.0", 599 | "sebastian/resource-operations": "~1.0", 600 | "sebastian/version": "~1.0.3|~2.0", 601 | "symfony/yaml": "~2.1|~3.0" 602 | }, 603 | "conflict": { 604 | "phpdocumentor/reflection-docblock": "3.0.2" 605 | }, 606 | "require-dev": { 607 | "ext-pdo": "*" 608 | }, 609 | "suggest": { 610 | "ext-xdebug": "*", 611 | "phpunit/php-invoker": "~1.1" 612 | }, 613 | "bin": [ 614 | "phpunit" 615 | ], 616 | "type": "library", 617 | "extra": { 618 | "branch-alias": { 619 | "dev-master": "5.7.x-dev" 620 | } 621 | }, 622 | "autoload": { 623 | "classmap": [ 624 | "src/" 625 | ] 626 | }, 627 | "notification-url": "https://packagist.org/downloads/", 628 | "license": [ 629 | "BSD-3-Clause" 630 | ], 631 | "authors": [ 632 | { 633 | "name": "Sebastian Bergmann", 634 | "email": "sebastian@phpunit.de", 635 | "role": "lead" 636 | } 637 | ], 638 | "description": "The PHP Unit Testing framework.", 639 | "homepage": "https://phpunit.de/", 640 | "keywords": [ 641 | "phpunit", 642 | "testing", 643 | "xunit" 644 | ], 645 | "time": "2017-06-23 12:45:27" 646 | }, 647 | { 648 | "name": "phpunit/phpunit-mock-objects", 649 | "version": "3.4.x-dev", 650 | "source": { 651 | "type": "git", 652 | "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", 653 | "reference": "a23b761686d50a560cc56233b9ecf49597cc9118" 654 | }, 655 | "dist": { 656 | "type": "zip", 657 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/a23b761686d50a560cc56233b9ecf49597cc9118", 658 | "reference": "a23b761686d50a560cc56233b9ecf49597cc9118", 659 | "shasum": "" 660 | }, 661 | "require": { 662 | "doctrine/instantiator": "^1.0.2", 663 | "php": "^5.6 || ^7.0", 664 | "phpunit/php-text-template": "^1.2", 665 | "sebastian/exporter": "^1.2 || ^2.0" 666 | }, 667 | "conflict": { 668 | "phpunit/phpunit": "<5.4.0" 669 | }, 670 | "require-dev": { 671 | "phpunit/phpunit": "^5.4" 672 | }, 673 | "suggest": { 674 | "ext-soap": "*" 675 | }, 676 | "type": "library", 677 | "extra": { 678 | "branch-alias": { 679 | "dev-master": "3.2.x-dev" 680 | } 681 | }, 682 | "autoload": { 683 | "classmap": [ 684 | "src/" 685 | ] 686 | }, 687 | "notification-url": "https://packagist.org/downloads/", 688 | "license": [ 689 | "BSD-3-Clause" 690 | ], 691 | "authors": [ 692 | { 693 | "name": "Sebastian Bergmann", 694 | "email": "sb@sebastian-bergmann.de", 695 | "role": "lead" 696 | } 697 | ], 698 | "description": "Mock Object library for PHPUnit", 699 | "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", 700 | "keywords": [ 701 | "mock", 702 | "xunit" 703 | ], 704 | "time": "2017-06-30 09:13:00" 705 | }, 706 | { 707 | "name": "sebastian/code-unit-reverse-lookup", 708 | "version": "dev-master", 709 | "source": { 710 | "type": "git", 711 | "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", 712 | "reference": "3488be0a7b346cd6e5361510ed07e88f9bea2e88" 713 | }, 714 | "dist": { 715 | "type": "zip", 716 | "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/3488be0a7b346cd6e5361510ed07e88f9bea2e88", 717 | "reference": "3488be0a7b346cd6e5361510ed07e88f9bea2e88", 718 | "shasum": "" 719 | }, 720 | "require": { 721 | "php": "^5.6 || ^7.0" 722 | }, 723 | "require-dev": { 724 | "phpunit/phpunit": "^5.7 || ^6.0" 725 | }, 726 | "type": "library", 727 | "extra": { 728 | "branch-alias": { 729 | "dev-master": "1.0.x-dev" 730 | } 731 | }, 732 | "autoload": { 733 | "classmap": [ 734 | "src/" 735 | ] 736 | }, 737 | "notification-url": "https://packagist.org/downloads/", 738 | "license": [ 739 | "BSD-3-Clause" 740 | ], 741 | "authors": [ 742 | { 743 | "name": "Sebastian Bergmann", 744 | "email": "sebastian@phpunit.de" 745 | } 746 | ], 747 | "description": "Looks up which function or method a line of code belongs to", 748 | "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", 749 | "time": "2017-03-04 10:23:55" 750 | }, 751 | { 752 | "name": "sebastian/comparator", 753 | "version": "1.2.x-dev", 754 | "source": { 755 | "type": "git", 756 | "url": "https://github.com/sebastianbergmann/comparator.git", 757 | "reference": "18a5d97c25f408f48acaf6d1b9f4079314c5996a" 758 | }, 759 | "dist": { 760 | "type": "zip", 761 | "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/18a5d97c25f408f48acaf6d1b9f4079314c5996a", 762 | "reference": "18a5d97c25f408f48acaf6d1b9f4079314c5996a", 763 | "shasum": "" 764 | }, 765 | "require": { 766 | "php": ">=5.3.3", 767 | "sebastian/diff": "~1.2", 768 | "sebastian/exporter": "~1.2 || ~2.0" 769 | }, 770 | "require-dev": { 771 | "phpunit/phpunit": "~4.4" 772 | }, 773 | "type": "library", 774 | "extra": { 775 | "branch-alias": { 776 | "dev-master": "1.2.x-dev" 777 | } 778 | }, 779 | "autoload": { 780 | "classmap": [ 781 | "src/" 782 | ] 783 | }, 784 | "notification-url": "https://packagist.org/downloads/", 785 | "license": [ 786 | "BSD-3-Clause" 787 | ], 788 | "authors": [ 789 | { 790 | "name": "Jeff Welch", 791 | "email": "whatthejeff@gmail.com" 792 | }, 793 | { 794 | "name": "Volker Dusch", 795 | "email": "github@wallbash.com" 796 | }, 797 | { 798 | "name": "Bernhard Schussek", 799 | "email": "bschussek@2bepublished.at" 800 | }, 801 | { 802 | "name": "Sebastian Bergmann", 803 | "email": "sebastian@phpunit.de" 804 | } 805 | ], 806 | "description": "Provides the functionality to compare PHP values for equality", 807 | "homepage": "http://www.github.com/sebastianbergmann/comparator", 808 | "keywords": [ 809 | "comparator", 810 | "compare", 811 | "equality" 812 | ], 813 | "time": "2017-03-07 10:34:43" 814 | }, 815 | { 816 | "name": "sebastian/diff", 817 | "version": "1.4.x-dev", 818 | "source": { 819 | "type": "git", 820 | "url": "https://github.com/sebastianbergmann/diff.git", 821 | "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4" 822 | }, 823 | "dist": { 824 | "type": "zip", 825 | "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7f066a26a962dbe58ddea9f72a4e82874a3975a4", 826 | "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4", 827 | "shasum": "" 828 | }, 829 | "require": { 830 | "php": "^5.3.3 || ^7.0" 831 | }, 832 | "require-dev": { 833 | "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" 834 | }, 835 | "type": "library", 836 | "extra": { 837 | "branch-alias": { 838 | "dev-master": "1.4-dev" 839 | } 840 | }, 841 | "autoload": { 842 | "classmap": [ 843 | "src/" 844 | ] 845 | }, 846 | "notification-url": "https://packagist.org/downloads/", 847 | "license": [ 848 | "BSD-3-Clause" 849 | ], 850 | "authors": [ 851 | { 852 | "name": "Kore Nordmann", 853 | "email": "mail@kore-nordmann.de" 854 | }, 855 | { 856 | "name": "Sebastian Bergmann", 857 | "email": "sebastian@phpunit.de" 858 | } 859 | ], 860 | "description": "Diff implementation", 861 | "homepage": "https://github.com/sebastianbergmann/diff", 862 | "keywords": [ 863 | "diff" 864 | ], 865 | "time": "2017-05-22 07:24:03" 866 | }, 867 | { 868 | "name": "sebastian/environment", 869 | "version": "2.0.x-dev", 870 | "source": { 871 | "type": "git", 872 | "url": "https://github.com/sebastianbergmann/environment.git", 873 | "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac" 874 | }, 875 | "dist": { 876 | "type": "zip", 877 | "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/5795ffe5dc5b02460c3e34222fee8cbe245d8fac", 878 | "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac", 879 | "shasum": "" 880 | }, 881 | "require": { 882 | "php": "^5.6 || ^7.0" 883 | }, 884 | "require-dev": { 885 | "phpunit/phpunit": "^5.0" 886 | }, 887 | "type": "library", 888 | "extra": { 889 | "branch-alias": { 890 | "dev-master": "2.0.x-dev" 891 | } 892 | }, 893 | "autoload": { 894 | "classmap": [ 895 | "src/" 896 | ] 897 | }, 898 | "notification-url": "https://packagist.org/downloads/", 899 | "license": [ 900 | "BSD-3-Clause" 901 | ], 902 | "authors": [ 903 | { 904 | "name": "Sebastian Bergmann", 905 | "email": "sebastian@phpunit.de" 906 | } 907 | ], 908 | "description": "Provides functionality to handle HHVM/PHP environments", 909 | "homepage": "http://www.github.com/sebastianbergmann/environment", 910 | "keywords": [ 911 | "Xdebug", 912 | "environment", 913 | "hhvm" 914 | ], 915 | "time": "2016-11-26 07:53:53" 916 | }, 917 | { 918 | "name": "sebastian/exporter", 919 | "version": "2.0.x-dev", 920 | "source": { 921 | "type": "git", 922 | "url": "https://github.com/sebastianbergmann/exporter.git", 923 | "reference": "5e8e30670c3f36481e75211dbbcfd029a41ebf07" 924 | }, 925 | "dist": { 926 | "type": "zip", 927 | "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/5e8e30670c3f36481e75211dbbcfd029a41ebf07", 928 | "reference": "5e8e30670c3f36481e75211dbbcfd029a41ebf07", 929 | "shasum": "" 930 | }, 931 | "require": { 932 | "php": "^5.3.3 || ^7.0", 933 | "sebastian/recursion-context": "^2.0" 934 | }, 935 | "require-dev": { 936 | "ext-mbstring": "*", 937 | "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" 938 | }, 939 | "type": "library", 940 | "extra": { 941 | "branch-alias": { 942 | "dev-master": "2.0.x-dev" 943 | } 944 | }, 945 | "autoload": { 946 | "classmap": [ 947 | "src/" 948 | ] 949 | }, 950 | "notification-url": "https://packagist.org/downloads/", 951 | "license": [ 952 | "BSD-3-Clause" 953 | ], 954 | "authors": [ 955 | { 956 | "name": "Jeff Welch", 957 | "email": "whatthejeff@gmail.com" 958 | }, 959 | { 960 | "name": "Volker Dusch", 961 | "email": "github@wallbash.com" 962 | }, 963 | { 964 | "name": "Bernhard Schussek", 965 | "email": "bschussek@2bepublished.at" 966 | }, 967 | { 968 | "name": "Sebastian Bergmann", 969 | "email": "sebastian@phpunit.de" 970 | }, 971 | { 972 | "name": "Adam Harvey", 973 | "email": "aharvey@php.net" 974 | } 975 | ], 976 | "description": "Provides the functionality to export PHP variables for visualization", 977 | "homepage": "http://www.github.com/sebastianbergmann/exporter", 978 | "keywords": [ 979 | "export", 980 | "exporter" 981 | ], 982 | "time": "2017-03-07 10:36:49" 983 | }, 984 | { 985 | "name": "sebastian/global-state", 986 | "version": "1.1.x-dev", 987 | "source": { 988 | "type": "git", 989 | "url": "https://github.com/sebastianbergmann/global-state.git", 990 | "reference": "cea85a84b00f2795341ebbbca4fa396347f2494e" 991 | }, 992 | "dist": { 993 | "type": "zip", 994 | "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/cea85a84b00f2795341ebbbca4fa396347f2494e", 995 | "reference": "cea85a84b00f2795341ebbbca4fa396347f2494e", 996 | "shasum": "" 997 | }, 998 | "require": { 999 | "php": ">=5.3.3" 1000 | }, 1001 | "require-dev": { 1002 | "phpunit/phpunit": "~4.2|~5.0" 1003 | }, 1004 | "suggest": { 1005 | "ext-uopz": "*" 1006 | }, 1007 | "type": "library", 1008 | "extra": { 1009 | "branch-alias": { 1010 | "dev-master": "1.0-dev" 1011 | } 1012 | }, 1013 | "autoload": { 1014 | "classmap": [ 1015 | "src/" 1016 | ] 1017 | }, 1018 | "notification-url": "https://packagist.org/downloads/", 1019 | "license": [ 1020 | "BSD-3-Clause" 1021 | ], 1022 | "authors": [ 1023 | { 1024 | "name": "Sebastian Bergmann", 1025 | "email": "sebastian@phpunit.de" 1026 | } 1027 | ], 1028 | "description": "Snapshotting of global state", 1029 | "homepage": "http://www.github.com/sebastianbergmann/global-state", 1030 | "keywords": [ 1031 | "global state" 1032 | ], 1033 | "time": "2017-02-23 14:11:06" 1034 | }, 1035 | { 1036 | "name": "sebastian/object-enumerator", 1037 | "version": "2.0.x-dev", 1038 | "source": { 1039 | "type": "git", 1040 | "url": "https://github.com/sebastianbergmann/object-enumerator.git", 1041 | "reference": "c956fe7a68318639f694fc6bba0c89b7cdf1b08c" 1042 | }, 1043 | "dist": { 1044 | "type": "zip", 1045 | "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/c956fe7a68318639f694fc6bba0c89b7cdf1b08c", 1046 | "reference": "c956fe7a68318639f694fc6bba0c89b7cdf1b08c", 1047 | "shasum": "" 1048 | }, 1049 | "require": { 1050 | "php": "^5.6 || ^7.0", 1051 | "sebastian/recursion-context": "^2.0" 1052 | }, 1053 | "require-dev": { 1054 | "phpunit/phpunit": "^5.7" 1055 | }, 1056 | "type": "library", 1057 | "extra": { 1058 | "branch-alias": { 1059 | "dev-master": "2.0.x-dev" 1060 | } 1061 | }, 1062 | "autoload": { 1063 | "classmap": [ 1064 | "src/" 1065 | ] 1066 | }, 1067 | "notification-url": "https://packagist.org/downloads/", 1068 | "license": [ 1069 | "BSD-3-Clause" 1070 | ], 1071 | "authors": [ 1072 | { 1073 | "name": "Sebastian Bergmann", 1074 | "email": "sebastian@phpunit.de" 1075 | } 1076 | ], 1077 | "description": "Traverses array structures and object graphs to enumerate all referenced objects", 1078 | "homepage": "https://github.com/sebastianbergmann/object-enumerator/", 1079 | "time": "2017-03-07 10:37:45" 1080 | }, 1081 | { 1082 | "name": "sebastian/recursion-context", 1083 | "version": "2.0.x-dev", 1084 | "source": { 1085 | "type": "git", 1086 | "url": "https://github.com/sebastianbergmann/recursion-context.git", 1087 | "reference": "7e4d7c56f6e65d215f71ad913a5256e5439aca1c" 1088 | }, 1089 | "dist": { 1090 | "type": "zip", 1091 | "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/7e4d7c56f6e65d215f71ad913a5256e5439aca1c", 1092 | "reference": "7e4d7c56f6e65d215f71ad913a5256e5439aca1c", 1093 | "shasum": "" 1094 | }, 1095 | "require": { 1096 | "php": ">=5.3.3" 1097 | }, 1098 | "require-dev": { 1099 | "phpunit/phpunit": "~4.4" 1100 | }, 1101 | "type": "library", 1102 | "extra": { 1103 | "branch-alias": { 1104 | "dev-master": "2.0.x-dev" 1105 | } 1106 | }, 1107 | "autoload": { 1108 | "classmap": [ 1109 | "src/" 1110 | ] 1111 | }, 1112 | "notification-url": "https://packagist.org/downloads/", 1113 | "license": [ 1114 | "BSD-3-Clause" 1115 | ], 1116 | "authors": [ 1117 | { 1118 | "name": "Jeff Welch", 1119 | "email": "whatthejeff@gmail.com" 1120 | }, 1121 | { 1122 | "name": "Sebastian Bergmann", 1123 | "email": "sebastian@phpunit.de" 1124 | }, 1125 | { 1126 | "name": "Adam Harvey", 1127 | "email": "aharvey@php.net" 1128 | } 1129 | ], 1130 | "description": "Provides functionality to recursively process PHP variables", 1131 | "homepage": "http://www.github.com/sebastianbergmann/recursion-context", 1132 | "time": "2017-03-08 08:21:15" 1133 | }, 1134 | { 1135 | "name": "sebastian/resource-operations", 1136 | "version": "dev-master", 1137 | "source": { 1138 | "type": "git", 1139 | "url": "https://github.com/sebastianbergmann/resource-operations.git", 1140 | "reference": "fadc83f7c41fb2924e542635fea47ae546816ece" 1141 | }, 1142 | "dist": { 1143 | "type": "zip", 1144 | "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/fadc83f7c41fb2924e542635fea47ae546816ece", 1145 | "reference": "fadc83f7c41fb2924e542635fea47ae546816ece", 1146 | "shasum": "" 1147 | }, 1148 | "require": { 1149 | "php": ">=5.6.0" 1150 | }, 1151 | "type": "library", 1152 | "extra": { 1153 | "branch-alias": { 1154 | "dev-master": "1.0.x-dev" 1155 | } 1156 | }, 1157 | "autoload": { 1158 | "classmap": [ 1159 | "src/" 1160 | ] 1161 | }, 1162 | "notification-url": "https://packagist.org/downloads/", 1163 | "license": [ 1164 | "BSD-3-Clause" 1165 | ], 1166 | "authors": [ 1167 | { 1168 | "name": "Sebastian Bergmann", 1169 | "email": "sebastian@phpunit.de" 1170 | } 1171 | ], 1172 | "description": "Provides a list of PHP built-in functions that operate on resources", 1173 | "homepage": "https://www.github.com/sebastianbergmann/resource-operations", 1174 | "time": "2016-10-03 07:43:09" 1175 | }, 1176 | { 1177 | "name": "sebastian/version", 1178 | "version": "dev-master", 1179 | "source": { 1180 | "type": "git", 1181 | "url": "https://github.com/sebastianbergmann/version.git", 1182 | "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" 1183 | }, 1184 | "dist": { 1185 | "type": "zip", 1186 | "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", 1187 | "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", 1188 | "shasum": "" 1189 | }, 1190 | "require": { 1191 | "php": ">=5.6" 1192 | }, 1193 | "type": "library", 1194 | "extra": { 1195 | "branch-alias": { 1196 | "dev-master": "2.0.x-dev" 1197 | } 1198 | }, 1199 | "autoload": { 1200 | "classmap": [ 1201 | "src/" 1202 | ] 1203 | }, 1204 | "notification-url": "https://packagist.org/downloads/", 1205 | "license": [ 1206 | "BSD-3-Clause" 1207 | ], 1208 | "authors": [ 1209 | { 1210 | "name": "Sebastian Bergmann", 1211 | "email": "sebastian@phpunit.de", 1212 | "role": "lead" 1213 | } 1214 | ], 1215 | "description": "Library that helps with managing the version number of Git-hosted PHP projects", 1216 | "homepage": "https://github.com/sebastianbergmann/version", 1217 | "time": "2016-10-03 07:35:21" 1218 | }, 1219 | { 1220 | "name": "symfony/yaml", 1221 | "version": "v2.8.2", 1222 | "source": { 1223 | "type": "git", 1224 | "url": "https://github.com/symfony/yaml.git", 1225 | "reference": "34c8a4b51e751e7ea869b8262f883d008a2b81b8" 1226 | }, 1227 | "dist": { 1228 | "type": "zip", 1229 | "url": "https://api.github.com/repos/symfony/yaml/zipball/34c8a4b51e751e7ea869b8262f883d008a2b81b8", 1230 | "reference": "34c8a4b51e751e7ea869b8262f883d008a2b81b8", 1231 | "shasum": "" 1232 | }, 1233 | "require": { 1234 | "php": ">=5.3.9" 1235 | }, 1236 | "type": "library", 1237 | "extra": { 1238 | "branch-alias": { 1239 | "dev-master": "2.8-dev" 1240 | } 1241 | }, 1242 | "autoload": { 1243 | "psr-4": { 1244 | "Symfony\\Component\\Yaml\\": "" 1245 | }, 1246 | "exclude-from-classmap": [ 1247 | "/Tests/" 1248 | ] 1249 | }, 1250 | "notification-url": "https://packagist.org/downloads/", 1251 | "license": [ 1252 | "MIT" 1253 | ], 1254 | "authors": [ 1255 | { 1256 | "name": "Fabien Potencier", 1257 | "email": "fabien@symfony.com" 1258 | }, 1259 | { 1260 | "name": "Symfony Community", 1261 | "homepage": "https://symfony.com/contributors" 1262 | } 1263 | ], 1264 | "description": "Symfony Yaml Component", 1265 | "homepage": "https://symfony.com", 1266 | "time": "2016-01-13 10:28:07" 1267 | }, 1268 | { 1269 | "name": "webmozart/assert", 1270 | "version": "dev-master", 1271 | "source": { 1272 | "type": "git", 1273 | "url": "https://github.com/webmozart/assert.git", 1274 | "reference": "4a8bf11547e139e77b651365113fc12850c43d9a" 1275 | }, 1276 | "dist": { 1277 | "type": "zip", 1278 | "url": "https://api.github.com/repos/webmozart/assert/zipball/4a8bf11547e139e77b651365113fc12850c43d9a", 1279 | "reference": "4a8bf11547e139e77b651365113fc12850c43d9a", 1280 | "shasum": "" 1281 | }, 1282 | "require": { 1283 | "php": "^5.3.3 || ^7.0" 1284 | }, 1285 | "require-dev": { 1286 | "phpunit/phpunit": "^4.6", 1287 | "sebastian/version": "^1.0.1" 1288 | }, 1289 | "type": "library", 1290 | "extra": { 1291 | "branch-alias": { 1292 | "dev-master": "1.3-dev" 1293 | } 1294 | }, 1295 | "autoload": { 1296 | "psr-4": { 1297 | "Webmozart\\Assert\\": "src/" 1298 | } 1299 | }, 1300 | "notification-url": "https://packagist.org/downloads/", 1301 | "license": [ 1302 | "MIT" 1303 | ], 1304 | "authors": [ 1305 | { 1306 | "name": "Bernhard Schussek", 1307 | "email": "bschussek@gmail.com" 1308 | } 1309 | ], 1310 | "description": "Assertions to validate method input/output with nice error messages.", 1311 | "keywords": [ 1312 | "assert", 1313 | "check", 1314 | "validate" 1315 | ], 1316 | "time": "2016-11-23 20:04:41" 1317 | } 1318 | ], 1319 | "aliases": [], 1320 | "minimum-stability": "dev", 1321 | "stability-flags": [], 1322 | "prefer-stable": false, 1323 | "prefer-lowest": false, 1324 | "platform": { 1325 | "php": ">=5.6.0" 1326 | }, 1327 | "platform-dev": [] 1328 | } 1329 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 13 | 14 | 15 | 16 | ./tests/ 17 | 18 | 19 | 20 | 21 | 22 | ./ 23 | 24 | ./tests 25 | ./vendor 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ## JetFire PHP Routing 2 | [![SensioLabsInsight](https://insight.sensiolabs.com/projects/aeb83524-44bc-4501-90dc-18041cc7a84a/mini.png)](https://insight.sensiolabs.com/projects/aeb83524-44bc-4501-90dc-18041cc7a84a) [![Build Status](https://travis-ci.org/jetfirephp/routing.svg?branch=master)](https://travis-ci.org/jetfirephp/routing) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/jetfirephp/routing/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/jetfirephp/routing/?branch=master) 3 | 4 | A simple & powerful router for PHP 5.4+ 5 | 6 | ### Features 7 | 8 | V1.3 9 | * [Support subdomain](#subdomain) 10 | * [Support group resolver](#group-resolver) 11 | 12 | V1.2 13 | * [ClosureTemplate resolver](#closureTemplate-resolver) 14 | * [ControllerTemplate resolver](#controllerTemplate-resolver) 15 | * Possibility to choose a resolver 16 | 17 | V1.1 18 | * [Support dependency injection container](#config) 19 | * [Add your custom matcher and dispatcher](#custom-matcher) 20 | 21 | V1.0 22 | * Support static & dynamic route patterns 23 | * [Support REST routing](#rest) 24 | * [Support reversed routing using named routes](#named-routes) 25 | * [Uri matcher](#uri-matcher) 26 | * [Array matcher](#array-matcher) 27 | * [Closure resolver](#closure-resolver) 28 | * [Template resolver](#template-resolver) 29 | * [Controller resolver](#controller-resolver) 30 | * [Route Middleware](#middleware) 31 | * [Integration with other libraries](#libraries) 32 | 33 | ### Getting started 34 | 35 | 1. PHP 5.4+ is required 36 | 2. Install `JetFire\Routing` using Composer 37 | 3. Setup URL rewriting so that all requests are handled by index.php 38 | 39 | ### Installation 40 | 41 | Via [composer](https://getcomposer.org) 42 | 43 | ```bash 44 | $ composer require jetfirephp/routing 45 | ``` 46 | 47 | #### .htaccess 48 | 49 | ```php 50 | RewriteEngine on 51 | RewriteCond %{REQUEST_FILENAME} !-f 52 | RewriteCond %{REQUEST_FILENAME} !-d 53 | RewriteRule ^(.[a-zA-Z0-9\-\_\/]*)$ index.php?url=$1 [QSA,L] 54 | ``` 55 | 56 | ### Usage 57 | 58 | Create an instance of `JetFire\Routing\RouteCollection` and define your routes. Then create an instance of `JetFire\Routing\Router` and run your routes. 59 | 60 | ```php 61 | // Require composer autoloader 62 | require __DIR__ . '/vendor/autoload.php'; 63 | 64 | // Create RouteCollection instance 65 | $collection = new \JetFire\Routing\RouteCollection(); 66 | 67 | // Define your routes 68 | // ... 69 | 70 | // Create an instance of Router 71 | $router = new \JetFire\Routing\Router($collection) 72 | 73 | // select your matcher 74 | $matcher1 = new \JetFire\Routing\Matcher\ArrayMatcher($router); 75 | $matcher2 = new \JetFire\Routing\Matcher\UriMatcher($router); 76 | 77 | // set your matcher to the router 78 | $router->setMatcher([$matcher1,$matcher2]) 79 | 80 | // Run it! 81 | $router->run(); 82 | ``` 83 | ### Matcher 84 | 85 | `JetFire\Routing` provide 2 type of matcher for your routes : `JetFire\Routing\Matcher\ArrayMatcher` and `JetFire\Routing\Matcher\UriMatcher` 86 | 87 | 88 | #### Uri Matcher 89 | 90 | With Uri Matcher you don't have to define your routes. Depending on the uri it can check if a target exist for the current url. 91 | But you have to define your views directory path and controllers namespace to the collection : 92 | 93 | ```php 94 | $options = [ 95 | 'view_dir' => '_VIEW_DIR_PATH_', 96 | 'ctrl_namespace' => '_CONTROLLERS_NAMESPACE_' 97 | ]; 98 | $collection = new \JetFire\Routing\RouteCollection(null,$options); 99 | // or 100 | $collection = new \JetFire\Routing\RouteCollection(); 101 | $collection->setOption($options); 102 | // or 103 | $collection = new \JetFire\Routing\RouteCollection(); 104 | $collection->addRoutes(null,$options) 105 | ``` 106 | 107 | For example if the uri is : `/home/index` 108 | 109 | ##### Resolver 110 | 111 | Here are the list of Uri Matcher resolver : 112 | 113 | ```php 114 | $resolver = [ 115 | 'isControllerAndTemplate', 116 | 'isController', 117 | 'isTemplate' 118 | ]; 119 | ``` 120 | 121 | ##### Template resolver 122 | 123 | Uri Matcher check if an `index.php` file exist in `/_VIEW_DIR_PATH_/Home` directory. 124 | 125 | If you want to check for other extension (html,json,...) You can configure the router like this : 126 | 127 | ```php 128 | $router->setConfig([ 129 | 130 | // Define your template extension like this 131 | 'templateExtension' => ['.php','.html','.twig','.json','.xml'], 132 | 133 | ]); 134 | ``` 135 | 136 | ##### Controller resolver 137 | 138 | With Controller resolver, Uri Matcher checks if a controller with name `HomeController` located in the namespace `_CONTROLLERS_NAMESPACE_` has the `index` method. 139 | You have to require your controller before matching or you can use your custom autoloader to load your controllers. 140 | Uri Matcher support also dynamic routes. For example if the uri is : `/home/user/peter/parker` then you must have a method `user` with two parameters like this : 141 | 142 | ```php 143 | class HomeController { 144 | public function user($firstName,$lastName){ 145 | // $firstName = peter 146 | // $lastName = parker 147 | } 148 | } 149 | ``` 150 | 151 | 152 | 153 | #### Array Matcher 154 | 155 | With Array Matcher you have to add your routes like this : 156 | 157 | ```php 158 | $options = [ 159 | 'view_dir' => '_VIEW_DIR_PATH_', 160 | 'ctrl_namespace' => '_CONTROLLERS_NAMESPACE_' 161 | ]; 162 | 163 | // addRoutes expect an array argument 164 | $collection->addRoutes([ 165 | '/home/index' => '_TARGET_' 166 | ],$options); 167 | 168 | // or a file containing an array 169 | $collection->addRoutes('path_to_array_file',$options); 170 | ``` 171 | 172 | We recommend that you define your routes in a separate file and pass the path to `addRoutes()` method. 173 | 174 | ```php 175 | // routes.php file 176 | return [ 177 | '/home/index' => '_TARGET_' 178 | ]; 179 | ``` 180 | 181 | ##### Resolver 182 | 183 | Here are the list of Uri Matcher resolver : 184 | 185 | ```php 186 | $resolver = [ 187 | 'isControllerAndTemplate', 188 | 'isClosureAndTemplate', 189 | 'isClosure', 190 | 'isController', 191 | 'isTemplate' 192 | ]; 193 | ``` 194 | 195 | You have 5 actions possible for Array Routing. We assume you are using a separate file for your routes. 196 | 197 | 198 | ##### Template resolver 199 | 200 | ```php 201 | return [ 202 | 203 | // static route 204 | '/home/index' => 'Home/index.php', 205 | 206 | // dynamic route with arguments 207 | '/home/user-:id-:slug' => [ 208 | 'use' => 'Home/page.html', 209 | 'arguments' => ['id' => '[0-9]+','slug' => '[a-z-]*'], 210 | ], 211 | 212 | ]; 213 | ``` 214 | 215 | ##### Controller resolver 216 | 217 | ```php 218 | return [ 219 | 220 | // static route 221 | '/home/index' => 'HomeController@index', 222 | 223 | // dynamic route with arguments 224 | '/home/user-:id-:slug' => [ 225 | 'use' => 'HomeController@page', 226 | 'arguments' => ['id' => '[0-9]+','slug' => '[a-z-]*'], 227 | ], 228 | 229 | ]; 230 | ``` 231 | 232 | ##### Controller and Template resolver 233 | 234 | ```php 235 | return [ 236 | 237 | // controller and template resolver 238 | // call first the controller and render then the template 239 | // if the template is not found, the controller is returned 240 | '/home/log' => [ 241 | 'use' => 'HomeController@log', 242 | 'template' => 'Home/log.php', //in your controller you can return an array of data that you can access in your template 243 | ], 244 | 245 | // dynamic route with arguments 246 | '/home/user-:id-:slug' => [ 247 | 'use' => 'HomeController@page', 248 | 'template' => 'Home/log.php', 249 | 'arguments' => ['id' => '[0-9]+','slug' => '[a-z-]*'], 250 | ], 251 | 252 | ]; 253 | ``` 254 | 255 | ##### Controller group resolver 256 | 257 | ```php 258 | return [ 259 | 260 | // suppose we have the following methods in the AccountController : 261 | // public function create(); 262 | // public function read($id); 263 | // public function update($id); 264 | // public function destroy($id); 265 | // if the uri is /account/create the router will call the associated method in the controller 266 | '/account/*' => [ 267 | 'use' => 'AccountController@{method}', 268 | ], 269 | 270 | ]; 271 | ``` 272 | 273 | ##### Closure resolver 274 | 275 | ```php 276 | return [ 277 | 278 | // static route 279 | '/home/index' => function(){ 280 | return 'Hello world !'; 281 | }, 282 | 283 | // dynamic route with arguments 284 | '/home/user-:id-:slug' => [ 285 | 'use' => function($id,$slug){ 286 | return 'Hello User '.$id.'-'.$slug; 287 | }, 288 | 'arguments' => ['id' => '[0-9]+','slug' => '[a-z-]*'], 289 | ], 290 | 291 | ]; 292 | ``` 293 | 294 | ##### Closure and Template resolver 295 | 296 | ```php 297 | return [ 298 | 299 | // closure and template matching 300 | // call first the closure and render then the template 301 | '/home/log' => [ 302 | 'use' => function(){ 303 | return ['name' => 'Peter']; 304 | } 305 | 'template' => 'Home/log.php', // in log.php you can access the return data like this : $name ='Peter' 306 | ], 307 | 308 | '/home/user-:id-:slug' => [ 309 | 'use' => function($id,$slug){ 310 | return ['id' => $id,'slug' => $slug]; 311 | }, 312 | 'template' => 'Home/log.php', 313 | 'arguments' => ['id' => '[0-9]+','slug' => '[a-z-]*'], 314 | ], 315 | 316 | ]; 317 | ``` 318 | 319 | ### Block Routes 320 | 321 | With `JetFire\Routing` you have the ability to create block routes to better organize your code. 322 | For example , if you have an administration for your website , you can create block only for this section and another block to the public part like this : 323 | 324 | ```php 325 | // Create RouteCollection instance 326 | $collection = new \JetFire\Routing\RouteCollection(); 327 | 328 | // Block routes 329 | $collection->addRoutes('admin_routes_path',['view_dir' => 'admin_view_path' , 'ctrl_namespace' => 'admin_controllers_namespace','prefix' => 'admin']); 330 | $collection->addRoutes('public_routes_path',['view_dir' => 'public_view_path' , 'ctrl_namespace' => 'public_controllers_namespace']); 331 | 332 | // Create an instance of Router 333 | $router = new \JetFire\Routing\Router($collection) 334 | // Select your matcher 335 | $router->addRouter(new \JetFire\Routing\Matcher\ArrayMatcher($router)); 336 | 337 | // Run it! 338 | $router->run(); 339 | ``` 340 | 341 | ### Router Configuration 342 | 343 | Here are the list of router configuration that you can edit : 344 | 345 | ```php 346 | $router->setConfig([ 347 | 348 | // You can add/remove extension for views 349 | // default extension for views 350 | 'templateExtension' => ['.html', '.php', '.json', '.xml'], 351 | 352 | // If you use template engine library, you can use this to render the view 353 | // See the 'Integration with other libraries' section for more details 354 | 'templateCallback' => [], 355 | 356 | // If you want to add a dependency injection container for your controllers constructor or method 357 | // for example if your controller 'HomeController' method 'log' method require a class like this : public function log(Request $request) 358 | // by default : 359 | 'di' => function($class){ 360 | return new $class; 361 | }, 362 | 363 | // See the Named Routes section for more details 364 | 'generateRoutesPath' => false, 365 | ]); 366 | ``` 367 | 368 | ### Collection Options 369 | 370 | Here are the list of options that you can edit for each collection routes : 371 | 372 | ```php 373 | $options = [ 374 | // your view directory 375 | 'view_dir' => 'view_directory', 376 | // your controllers namespace 377 | 'ctrl_namespace' => 'controllers_namespace', 378 | // your routes prefix 379 | 'prefix' => 'your_prefix' 380 | ]; 381 | ``` 382 | 383 | 384 | ### Named Routes 385 | 386 | You can specify a name for each route like this : 387 | 388 | ```php 389 | return [ 390 | 391 | '/home/index' => [ 392 | 'use' => 'Home/index.html', 393 | 'name' => 'home.index' 394 | ], 395 | 396 | '/home/user-:id-:slug' => [ 397 | 'use' => 'HomeController@user', 398 | 'name' => 'home.user', 399 | 'arguments' => ['id' => '[0-9]+','slug' => '[a-z-]*'], 400 | ], 401 | ]; 402 | ``` 403 | And then to get the url of this route you can do like this : 404 | 405 | ```php 406 | // You have to enable generateRoutesPath to get routes url 407 | $router->setConfig([ 408 | 'generateRoutesPath' => true, 409 | // Other configuration 410 | // ... 411 | ]); 412 | 413 | // Reverse routing 414 | $collection->getRoutePath('home.index'); // return http://your_domain/home/index 415 | $collection->getRoutePath('home.user',[ 'id' => 1, 'slug' => 'toto']); // return http://your_domain/home/user-1-toto 416 | ``` 417 | 418 | Supported only in `JetFire\Routing\Matcher\ArrayMatcher`. 419 | 420 | ### REST Routing 421 | 422 | You can specify the request method for each route like this : 423 | 424 | ```php 425 | return [ 426 | 427 | '/api/users' => [ 428 | 'use' => [ 429 | 'GET' => function($response){ 430 | $response->setHeaders(['Content-Type' => 'application/json']); 431 | return ['name' => 'Peter']; 432 | }, 433 | 'POST' => function($response){ 434 | $response->setHeaders(['Content-Type' => 'application/json']); 435 | $response->setStatusCode(201); 436 | return []; 437 | }, 438 | ], 439 | 'name' => 'api.users', 440 | 'method' => ['GET', 'POST'] 441 | ], 442 | 443 | '/api/users/:id' => [ 444 | 'use' => [ 445 | 'GET' => function($response){ 446 | $response->setHeaders(['Content-Type' => 'application/json']); 447 | return ['name' => 'Peter']; 448 | }, 449 | 'PUT' => function($response){ 450 | $response->setHeaders(['Content-Type' => 'application/json']); 451 | return []; 452 | }, 453 | ], 454 | 'name' => 'api.user', 455 | 'arguments' => ['id' => '[0-9]+'], 456 | 'method' => ['GET','PUT'] 457 | ], 458 | ]; 459 | ``` 460 | 461 | ### Prefix 462 | 463 | You can set a prefix for each routes collection like this : 464 | 465 | ```php 466 | $collection->addRoutes('routes_file_1',['prefix' => 'prefix_1']); // all routes in routes_file_1 begin with prefix_1/ 467 | $collection->addRoutes('routes_file_2',['prefix' => 'prefix_2']); // all routes in routes_file_2 begin with prefix_2/ 468 | ``` 469 | Or : 470 | ```php 471 | $collection->addRoutes('routes_file_1'); 472 | $collection->addRoutes('routes_file_2'); 473 | $collection->setPrefix(['prefix_1','prefix_2']); 474 | ``` 475 | 476 | ### Middleware 477 | 478 | Middlewares are called before and after a route match the current uri. 479 | You have to create a middleware config file like this : 480 | 481 | ```php 482 | // Your middleware file 483 | return [ 484 | 485 | // global middleware are called every time 486 | 'global_middleware' => [ 487 | // Here you define your middleware class to be called 488 | 'app\Middleware\Global', 489 | ], 490 | 491 | // block middleware are called when the current route block match one of the following block 492 | 'block_middleware' => [ 493 | // You define here for each block the middleware class to be called 494 | '/app/Blocks/PublicBlock/' => 'app\Middleware\Public', 495 | '/app/Blocks/AdminBlock/' => 'app\Middleware\Admin', 496 | '/app/Blocks/UserBlock/' => 'app\Middleware\User', 497 | ], 498 | 499 | // class middleware are called when the controller router match one of the following controller 500 | 'class_middleware' => [ 501 | // You define here for each controller the middleware class to be called 502 | '/app/Blocks/PublicBlock/Controllers/HomeController' => 'app\Middleware\Home', 503 | ], 504 | 505 | // route middleware are called when the current route match one of the following middleware name 506 | 'route_middleware' => [ 507 | // You define here a name to the middleware and assign the class to be called 508 | // You have to specify this name to the route like this : `'middleware' => 'home'` 509 | 'home' => 'app\Middleware\App' 510 | ], 511 | 512 | ]; 513 | ``` 514 | 515 | Then you have to instantiate the middleware class `Middleware` like this : 516 | 517 | ```php 518 | $middleware = new Middleware($router); 519 | $middleware->setCallbackAction('before', 'your_before_middleware_file'); 520 | $middleware->setCallbackAction('between', 'your_between_middleware_file'); 521 | $middleware->setCallbackAction('after', 'your_after_middleware_file'); 522 | 523 | $router->addMiddleware($middleware); 524 | ``` 525 | 526 | Let see how to create your Middleware Class. For example we take the Global middleware : 527 | 528 | ```php 529 | namespace app\Middleware; 530 | 531 | class Global{ 532 | 533 | // Middleware class must implements handle method 534 | // object passed in argument will be inject automatically 535 | public function handle(){ 536 | // here you put your code 537 | // ... 538 | } 539 | } 540 | ``` 541 | See the API section to learn how to handle your $route in middleware class. 542 | 543 | 544 | ### Custom Matcher and Dispatcher 545 | 546 | If the default matcher and dispatcher doesn't match your expectation, you can write your own matcher and dispatcher like this : 547 | 548 | ```php 549 | class MyCustomMatcher implements MatcherInterface{ 550 | 551 | public function __construct(Router $router); 552 | 553 | // in this method you can check if the current uri match your expectation 554 | // return true or false 555 | // if it match you have to set your route target with an array of params and the dispatcher class name to be called 556 | // $this->setTarget(['dispatcher' => '\My\Custom\Dispatcher\Class\Name', 'other_params' => 'values']); 557 | public function match(); 558 | 559 | // set your route target $this->router->route->setTarget($target); 560 | public function setTarget($target = []); 561 | 562 | // set your resolver 563 | public function setResolver($resolver = []); 564 | 565 | // you can add multiple resolver method in the same matcher 566 | public function addResolver($resolver); 567 | 568 | // to retrieve your resolver 569 | public function getResolver(); 570 | 571 | // dispatcher yo be called 572 | public function setDispatcher($dispatcher = []); 573 | 574 | public function addDispatcher($dispatcher); 575 | } 576 | 577 | class MyCustomDispatcher implements DispatcherInterface{ 578 | 579 | public function __construct(Router $router); 580 | 581 | // your target to call 582 | // you can get your route target information with $this->route->getTarget() 583 | public function call(); 584 | } 585 | 586 | $router->addMatcher('MyCustomMatcher'); 587 | ``` 588 | 589 | You can also override the default matcher like this : 590 | 591 | ```php 592 | class MyCustomMatcher extends ArrayMatcher implements MatcherInterface{ 593 | 594 | public function __construct(Router $router){ 595 | parent::__construct($router); 596 | // your custom match method 597 | $this->addResolver('customResolver'); 598 | } 599 | 600 | public function customResolver(){ 601 | // your code here 602 | // ... 603 | // then you set the route target with the dispatcher 604 | } 605 | } 606 | 607 | class MyCustomDispatcher implements DispatcherInterface{ 608 | 609 | public function __construct(Router $router); 610 | 611 | // your target to call 612 | // you can get your route target information with $this->route->getTarget() 613 | public function call(); 614 | } 615 | ``` 616 | 617 | 618 | ### Integration with other libraries 619 | 620 | If you want to integrate other template engine libraries like twig, smarty ... you have to set the 'templateCallback' in router. 621 | 622 | ```php 623 | // Twig template engine 624 | require_once '/path/to/lib/Twig/Autoloader.php'; 625 | Twig_Autoloader::register(); 626 | 627 | // Other template engine 628 | $tpl = new \Acme\Template\Template(); 629 | 630 | $router->setConfig([ 631 | 'templateCallback' => [ 632 | 633 | // if the router find a template with twig enxtension then it will call the twig template engine 634 | 'twig' => function($route){ 635 | $loader = new Twig_Loader_Filesystem($route->getTarget('block')); 636 | $twig = new Twig_Environment($loader, []); 637 | $template = $twig->loadTemplate($route->getTarget('template')); 638 | echo $template->render($route->getData()); 639 | }, 640 | 641 | // for other template engine 642 | 'tpl' => function($route) use ($tpl){ 643 | $tpl->load($route->getTarget('template')); 644 | $tpl->setData($route->getData()); 645 | $tpl->display(); 646 | } 647 | ], 648 | 649 | // Other configuration 650 | // ... 651 | ]); 652 | ``` 653 | 654 | 655 | ### Subdomain 656 | 657 | ```php 658 | return [ 659 | '{subdomain}.{host}/home' => [ 660 | 'use' => 'AdminController@index', 661 | 'name' => 'admin.home.index', 662 | 'subdomain' => 'admin' // could be a regex for multiple subdomain 663 | ] 664 | ]; 665 | ``` 666 | 667 | Or if you want to add a subdomain for a bloc, you have to add this line in your route collection options : 668 | 669 | ```php 670 | $options = [ 671 | // ... 672 | 'subdomain' => 'your_subdomain' 673 | ]; 674 | ``` 675 | 676 | ### API 677 | 678 | Below is a list of the public methods and variables in the common classes you will most likely use. 679 | 680 | ```php 681 | // JetFire\Routing\RouteCollection 682 | $collection-> 683 | routesByName // routes url by their name 684 | countRoutes // count routes block 685 | addRoutes($collection,$options = []) // set your routes 686 | getRoutes($key = null) // return all routes 687 | getRoutePath($name,$params = []) // return the url of route 688 | setPrefix($prefix) // $prefix can be a string (applied for every collection) 689 | // or an array (for each collection you can specify a prefix) 690 | setOption($options = []) // set your routes option 691 | generateRoutesPath() // generate routes url by their name 692 | 693 | // JetFire\Routing\Router 694 | $router-> 695 | response // JetFire\Routing\ResponseInterface instance 696 | route // JetFire\Routing\Route instance 697 | collection // JetFire\Routing\RouteCollection instance 698 | middlewareCollection // middleware collection 699 | matcher // list of matcher 700 | dispatcher // the dispatcher instance 701 | setConfig($config) // router configuration 702 | getConfig() // get router configuration 703 | run() // run the router with the request url 704 | 705 | // JetFire\Routing\Route 706 | $route-> 707 | set($args = []) // set your route array parameters 708 | getUrl() // return the route url 709 | setUrl($url) // set the route url 710 | getName() // return the route name 711 | setName($name) // set the route name 712 | getCallback() // return the route callback (template,controller or anonymous function) 713 | setCallback($callback) // set the route callback 714 | getMethod() // return the route method (GET,POST,PUT,DELETE) 715 | getDetail() // return the route detail 716 | setDetail($detail) // set the route detail 717 | addDetail($key,$value) // add a detail for the route 718 | getTarget($key = null) // return the route target (dispatcher,template or controller|action or function) 719 | setTarget($target = []) // set the route target 720 | hasTarget($key = null) // check if the route has the following target 721 | getData() // return data for the route 722 | __call($name,$arguments) // magic call to get or set route detail 723 | 724 | ``` 725 | 726 | ### License 727 | 728 | The JetFire Routing is released under the MIT public license : http://www.opensource.org/licenses/MIT. 729 | -------------------------------------------------------------------------------- /src/Routing/Dispatcher/ClosureDispatcher.php: -------------------------------------------------------------------------------- 1 | router = $router; 28 | } 29 | 30 | 31 | /** 32 | * @description call anonymous function 33 | */ 34 | public function call() 35 | { 36 | $classInstance = [ 37 | Response::class => $this->router->response, 38 | Route::class => $this->router->route, 39 | RouteCollection::class => $this->router->collection, 40 | ]; 41 | 42 | $params = empty($this->router->route->getParameters()) ? $classInstance : array_merge($this->router->route->getParameters(), $classInstance); 43 | $content = call_user_func_array($this->router->route->getTarget('closure'), $params); 44 | if ($content instanceof ResponseInterface) { 45 | $this->router->response = $content; 46 | } else { 47 | if (is_array($content)) { 48 | $this->router->route->addTarget('data', $content); 49 | $content = json_encode($content); 50 | } 51 | $this->router->callMiddleware('between'); 52 | $this->router->response->setContent($content); 53 | } 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/Routing/Dispatcher/ControllerDispatcher.php: -------------------------------------------------------------------------------- 1 | router = $router; 32 | } 33 | 34 | 35 | /** 36 | * @throws \Exception 37 | */ 38 | public function call() 39 | { 40 | 41 | if (!class_exists($this->router->route->getTarget('controller'))) { 42 | throw new \Exception('Class not found : "' . $this->router->route->getTarget('controller') . '"'); 43 | } 44 | 45 | $classInstance = [ 46 | Route::class => $this->router->route, 47 | Response::class => $this->router->response, 48 | RouteCollection::class => $this->router->collection, 49 | ]; 50 | 51 | $reflectionMethod = new ReflectionMethod($this->router->route->getTarget('controller'), $this->router->route->getTarget('action')); 52 | $dependencies = []; 53 | $count = 0; 54 | 55 | foreach ($reflectionMethod->getParameters() as $arg) { 56 | if (!is_null($arg->getClass())) { 57 | if (isset($classInstance[$arg->getClass()->name])) { 58 | $dependencies[] = $classInstance[$arg->getClass()->name]; 59 | } else { 60 | $dependencies[] = call_user_func_array($this->router->route->getTarget('di'), [$arg->getClass()->name]); 61 | } 62 | } else { 63 | $count++; 64 | } 65 | } 66 | 67 | if ($count == count($this->router->route->getParameters()) || ($this->router->route->getParameters() == '' && $count == 0)) { 68 | $dependencies = array_merge($dependencies, ($this->router->route->getParameters() == '') ? [] : $this->router->route->getParameters()); 69 | $content = $reflectionMethod->invokeArgs($this->getController($classInstance), $dependencies); 70 | if ($content instanceof ResponseInterface) { 71 | $this->router->response = $content; 72 | } else { 73 | if (is_array($content)) { 74 | $this->router->route->addTarget('data', $content); 75 | $content = json_encode($content); 76 | } 77 | $this->router->callMiddleware('between'); 78 | $this->router->response->setContent($content); 79 | } 80 | } else { 81 | $this->router->response->setStatusCode(404); 82 | } 83 | } 84 | 85 | 86 | /** 87 | * @param array $classInstance 88 | * @return object 89 | * @throws \Exception 90 | */ 91 | private function getController($classInstance = []) 92 | { 93 | $reflector = new ReflectionClass($this->router->route->getTarget('controller')); 94 | if (!$reflector->isInstantiable()) { 95 | throw new \Exception('Target [' . $this->router->route->getTarget('controller') . '] is not instantiable.'); 96 | } 97 | $constructor = $reflector->getConstructor(); 98 | if (is_null($constructor)) { 99 | $class = $this->router->route->getTarget('controller'); 100 | return call_user_func_array($this->router->route->getTarget('di'), [$class]); 101 | } 102 | $dependencies = []; 103 | foreach ($constructor->getParameters() as $dep) { 104 | $class = $dep->getClass()->name; 105 | if (isset($classInstance[$class])) { 106 | $dependencies[] = $classInstance[$class]; 107 | } else { 108 | $dependencies[] = call_user_func_array($this->router->route->getTarget('di'), [$class]); 109 | } 110 | } 111 | return $reflector->newInstanceArgs($dependencies); 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /src/Routing/Dispatcher/DispatcherInterface.php: -------------------------------------------------------------------------------- 1 | 'application/json', 24 | 'xml' => 'application/xml', 25 | 'txt' => 'text/plain', 26 | 'html' => 'text/html' 27 | ]; 28 | 29 | /** 30 | * @param Router $router 31 | */ 32 | public function __construct(Router $router) 33 | { 34 | $this->router = $router; 35 | } 36 | 37 | /** 38 | * @description call template file 39 | */ 40 | public function call() 41 | { 42 | if (!is_file($this->router->route->getTarget('template'))) { 43 | throw new \Exception('Template file not found : "' . $this->router->route->getTarget('template') . '"'); 44 | } 45 | $this->setContentType($this->router->route->getTarget('extension')); 46 | if (isset($this->router->route->getTarget('callback')[$this->router->route->getTarget('extension')])) { 47 | $this->router->response->setContent(call_user_func_array($this->router->route->getTarget('callback')[$this->router->route->getTarget('extension')], [$this->router->route])); 48 | } else { 49 | ob_start(); 50 | if (isset($this->router->route->getTarget()['data'])) extract($this->router->route->getTarget('data')); 51 | if (isset($this->router->route->getParams()['data'])) extract($this->router->route->getParams()['data']); 52 | require($this->router->route->getTarget('template')); 53 | $this->router->response->setContent(ob_get_clean()); 54 | } 55 | } 56 | 57 | /** 58 | * @param $extension 59 | */ 60 | public function setContentType($extension) 61 | { 62 | isset($this->types[$extension]) 63 | ? $this->router->response->setHeaders(['Content-Type' => $this->types[$extension]]) 64 | : $this->router->response->setHeaders(['Content-Type' => $this->types['html']]); 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/Routing/Matcher/ArrayMatcher.php: -------------------------------------------------------------------------------- 1 | ClosureDispatcher::class, 37 | 'isController' => ControllerDispatcher::class, 38 | 'isTemplate' => TemplateDispatcher::class, 39 | 'isControllerAndTemplate' => [ControllerDispatcher::class, TemplateDispatcher::class], 40 | 'isClosureAndTemplate' => [ClosureDispatcher::class, TemplateDispatcher::class], 41 | ]; 42 | 43 | /** 44 | * @param Router $router 45 | */ 46 | public function __construct(Router $router) 47 | { 48 | $this->router = $router; 49 | } 50 | 51 | /** 52 | * @param array $resolver 53 | */ 54 | public function setResolver($resolver = []) 55 | { 56 | $this->resolver = $resolver; 57 | } 58 | 59 | /** 60 | * @param string $resolver 61 | */ 62 | public function addResolver($resolver) 63 | { 64 | $this->resolver[] = $resolver; 65 | } 66 | 67 | /** 68 | * @return array 69 | */ 70 | public function getResolver() 71 | { 72 | return $this->resolver; 73 | } 74 | 75 | /** 76 | * @param array $dispatcher 77 | */ 78 | public function setDispatcher($dispatcher = []) 79 | { 80 | $this->dispatcher = $dispatcher; 81 | } 82 | 83 | /** 84 | * @param $method 85 | * @param $class 86 | * @internal param array $dispatcher 87 | */ 88 | public function addDispatcher($method, $class) 89 | { 90 | $this->dispatcher[$method] = $class; 91 | } 92 | 93 | /** 94 | * @return bool 95 | */ 96 | public function match() 97 | { 98 | $this->request = []; 99 | for ($i = 0; $i < $this->router->collection->countRoutes; ++$i) { 100 | $this->request['prefix'] = ($this->router->collection->getRoutes('prefix_' . $i) != '') ? $this->router->collection->getRoutes('prefix_' . $i) : ''; 101 | $this->request['subdomain'] = ($this->router->collection->getRoutes('subdomain_' . $i) != '') ? $this->router->collection->getRoutes('subdomain_' . $i) : ''; 102 | foreach ($this->router->collection->getRoutes('routes_' . $i) as $route => $params) { 103 | $this->request['params'] = $params; 104 | $this->request['collection_index'] = $i; 105 | if ($this->checkSubdomain($route)) { 106 | $route = strstr($route, '/'); 107 | $this->request['route'] = preg_replace_callback('#:([\w]+)#', [$this, 'paramMatch'], '/' . trim(trim($this->request['prefix'], '/') . '/' . trim($route, '/'), '/')); 108 | if ($this->routeMatch('#^' . $this->request['route'] . '$#')) { 109 | $this->setCallback(); 110 | return $this->generateTarget(); 111 | } 112 | } 113 | } 114 | } 115 | return false; 116 | } 117 | 118 | /** 119 | * @param $route 120 | * @return bool 121 | */ 122 | private function checkSubdomain($route) 123 | { 124 | $url = (isset($_SERVER['REQUEST_SCHEME']) ? $_SERVER['REQUEST_SCHEME'] : 'http') . '://' . ($host = (isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : $_SERVER['HTTP_HOST'])); 125 | $host = explode(':', $host)[0]; 126 | $domain = $this->router->collection->getDomain($url); 127 | if (!empty($this->request['subdomain']) && $route[0] == '/') $route = trim($this->request['subdomain'], '.') . '.' . $domain . $route; 128 | if ($route[0] == '/') { 129 | return ($host != $domain) ? false : true; 130 | } elseif ($route[0] != '/' && $host != $domain) { 131 | $route = substr($route, 0, strpos($route, "/")); 132 | $route = str_replace('{host}', $domain, $route); 133 | $route = preg_replace_callback('#{subdomain}#', [$this, 'subdomainMatch'], $route); 134 | if (preg_match('#^' . $route . '$#', $host, $this->request['called_subdomain'])) { 135 | $this->request['called_subdomain'] = array_shift($this->request['called_subdomain']); 136 | $this->request['subdomain'] = str_replace('.' . $domain, '', $host); 137 | return true; 138 | } 139 | } 140 | return false; 141 | } 142 | 143 | /** 144 | * @return string 145 | */ 146 | private function subdomainMatch() 147 | { 148 | if (is_array($this->request['params']) && isset($this->request['params']['subdomain'])) { 149 | return '(' . $this->request['params']['subdomain'] . ')'; 150 | } 151 | return '([^/]+)'; 152 | } 153 | 154 | /** 155 | * @param $match 156 | * @return string 157 | */ 158 | private function paramMatch($match) 159 | { 160 | if (is_array($this->request['params']) && isset($this->request['params']['arguments'][$match[1]])) { 161 | $this->request['params']['arguments'][$match[1]] = str_replace('(', '(?:', $this->request['params']['arguments'][$match[1]]); 162 | return '(' . $this->request['params']['arguments'][$match[1]] . ')'; 163 | } 164 | if(isset($this->router->collection->getRoutes('params_' . $this->request['collection_index'])['arguments'][$match[1]])){ 165 | $this->request['params']['arguments'][$match[1]] = str_replace('(', '(?:', $this->router->collection->getRoutes('params_' . $this->request['collection_index'])['arguments'][$match[1]]); 166 | return '(' . $this->request['params']['arguments'][$match[1]] . ')'; 167 | } 168 | return '([^/]+)'; 169 | } 170 | 171 | /** 172 | * @param $regex 173 | * @return bool 174 | */ 175 | private function routeMatch($regex) 176 | { 177 | $regex = (substr($this->request['route'], -1) == '*') ? '#^' . $this->request['route'] . '#' : $regex; 178 | if (preg_match($regex, $this->router->route->getUrl(), $this->request['parameters'])) { 179 | array_shift($this->request['parameters']); 180 | return true; 181 | } 182 | return false; 183 | } 184 | 185 | /** 186 | * @return bool 187 | */ 188 | private function generateTarget() 189 | { 190 | if ($this->validMethod()) { 191 | foreach ($this->resolver as $resolver) { 192 | if (is_array($target = call_user_func_array([$this, $resolver], [$this->router->route->getCallback()]))) { 193 | $this->setTarget($target); 194 | return true; 195 | } 196 | } 197 | } 198 | $this->router->response->setStatusCode(405); 199 | return false; 200 | } 201 | 202 | /** 203 | * @param array $target 204 | */ 205 | public function setTarget($target = []) 206 | { 207 | $index = isset($this->request['collection_index']) ? $this->request['collection_index'] : 0; 208 | $this->checkRequest('subdomain'); 209 | $this->checkRequest('prefix'); 210 | $this->router->route->setDetail($this->request); 211 | $this->router->route->setTarget($target); 212 | $this->router->route->addTarget('block', $this->router->collection->getRoutes('block_' . $index)); 213 | $this->router->route->addTarget('view_dir', $this->router->collection->getRoutes('view_dir_' . $index)); 214 | $this->router->route->addTarget('params', $this->router->collection->getRoutes('params_' . $index)); 215 | } 216 | 217 | /** 218 | * @param $key 219 | */ 220 | private function checkRequest($key) 221 | { 222 | if (strpos($this->request[$key], ':') !== false && isset($this->request['parameters'][0])) { 223 | $replacements = $this->request['parameters']; 224 | $keys = []; 225 | $this->request['@' . $key] = $this->request[$key]; 226 | $this->request[$key] = preg_replace_callback('#:([\w?]+)#', function ($matches) use (&$replacements, &$keys) { 227 | $route_key = preg_replace("/[^A-Za-z0-9\\-_:]/", '', $matches[0]); 228 | $keys[$route_key] = isset($replacements[0]) ? $replacements[0] : null; 229 | return is_null($keys[$route_key]) ? '' : array_shift($replacements); 230 | }, $this->request[$key]); 231 | $this->request['keys'] = $keys; 232 | $this->request['parameters'] = $replacements; 233 | } 234 | } 235 | 236 | /** 237 | * 238 | */ 239 | private function setCallback() 240 | { 241 | if (isset($this->request['params'])) { 242 | if (is_callable($this->request['params'])) { 243 | $this->router->route->setCallback($this->request['params']); 244 | } else { 245 | if (is_array($this->request['params']) && isset($this->request['params']['use'])) { 246 | if (is_array($this->request['params']['use']) && isset($this->request['params']['use'][$this->router->route->getMethod()])) { 247 | $this->router->route->setCallback($this->request['params']['use'][$this->router->route->getMethod()]); 248 | } elseif (!is_array($this->request['params']['use'])) { 249 | $this->router->route->setCallback($this->request['params']['use']); 250 | } 251 | } else { 252 | $this->router->route->setCallback($this->request['params']); 253 | } 254 | if (isset($this->request['params']['name'])) { 255 | $this->router->route->setName($this->request['params']['name']); 256 | } 257 | if (isset($this->request['params']['method'])) { 258 | $this->request['params']['method'] = is_array($this->request['params']['method']) ? $this->request['params']['method'] : [$this->request['params']['method']]; 259 | } 260 | } 261 | } 262 | } 263 | 264 | /** 265 | * @return bool 266 | */ 267 | public function validMethod() 268 | { 269 | if (is_callable($this->request['params'])) { 270 | return true; 271 | } 272 | if (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') { 273 | return (isset($this->request['params']['ajax']) && $this->request['params']['ajax'] === true) ? true : false; 274 | } 275 | $method = (isset($this->request['params']['method'])) ? $this->request['params']['method'] : ['GET']; 276 | return (in_array($this->router->route->getMethod(), $method)) ? true : false; 277 | } 278 | 279 | /** 280 | * @param $callback 281 | * @return array|bool 282 | * @throws \Exception 283 | */ 284 | public function isClosureAndTemplate($callback) 285 | { 286 | if (is_array($cls = $this->isClosure($callback))) { 287 | if (is_array($this->request['params']) && isset($this->request['params']['template']) && is_array($tpl = $this->isTemplate($this->request['params']['template']))) { 288 | return array_merge(array_merge($cls, $tpl), [ 289 | 'dispatcher' => $this->dispatcher['isClosureAndTemplate'] 290 | ]); 291 | } 292 | return $cls; 293 | } 294 | return false; 295 | } 296 | 297 | /** 298 | * @param $callback 299 | * @return array|bool 300 | * @throws \Exception 301 | */ 302 | public function isControllerAndTemplate($callback) 303 | { 304 | if (is_array($ctrl = $this->isController($callback))) { 305 | if (is_array($this->request['params']) && isset($this->request['params']['template']) && is_array($tpl = $this->isTemplate($this->request['params']['template']))) { 306 | return array_merge(array_merge($ctrl, $tpl), [ 307 | 'dispatcher' => $this->dispatcher['isControllerAndTemplate'] 308 | ]); 309 | } 310 | return $ctrl; 311 | } 312 | return false; 313 | } 314 | 315 | 316 | /** 317 | * @param $callback 318 | * @return bool|array 319 | */ 320 | public function isClosure($callback) 321 | { 322 | if (is_callable($callback)) { 323 | return [ 324 | 'dispatcher' => $this->dispatcher['isClosure'], 325 | 'closure' => $callback 326 | ]; 327 | } 328 | return false; 329 | } 330 | 331 | /** 332 | * @param $callback 333 | * @throws \Exception 334 | * @return bool|array 335 | */ 336 | public function isController($callback) 337 | { 338 | if (is_string($callback) && strpos($callback, '@') !== false) { 339 | $routes = explode('@', $callback); 340 | if (!isset($routes[1])) $routes[1] = 'index'; 341 | if ($routes[1] == '{method}') { 342 | $params = explode('/', trim(preg_replace('#' . rtrim(str_replace('*', '', $this->request['route']), '/') . '#', '', $this->router->route->getUrl()), '/')); 343 | $routes[1] = empty($params[0]) ? 'index' : $params[0]; 344 | $this->request['@method'] = $routes[1]; 345 | array_shift($params); 346 | $this->request['parameters'] = array_merge($this->request['parameters'], $params); 347 | if (preg_match('/[A-Z]/', $routes[1])) return false; 348 | $routes[1] = lcfirst(str_replace(' ', '', ucwords(str_replace('-', ' ', $routes[1])))); 349 | } 350 | $index = isset($this->request['collection_index']) ? $this->request['collection_index'] : 0; 351 | $class = (class_exists($routes[0])) 352 | ? $routes[0] 353 | : $this->router->collection->getRoutes()['ctrl_namespace_' . $index] . $routes[0]; 354 | if (method_exists($class, $routes[1])) { 355 | return [ 356 | 'dispatcher' => $this->dispatcher['isController'], 357 | 'di' => $this->router->getConfig()['di'], 358 | 'controller' => $class, 359 | 'action' => $routes[1] 360 | ]; 361 | } 362 | if (!strpos($callback, '{method}') !== false) { 363 | throw new \Exception('The required method "' . $routes[1] . '" is not found in "' . $class . '"'); 364 | } 365 | } 366 | return false; 367 | } 368 | 369 | /** 370 | * @param $callback 371 | * @throws \Exception 372 | * @return bool|array 373 | */ 374 | public function isTemplate($callback) 375 | { 376 | if (is_string($callback) && strpos($callback, '@') === false) { 377 | $replace = isset($this->request['@method']) ? str_replace('-', '_', $this->request['@method']) : 'index'; 378 | $path = str_replace('{template}', $replace, trim($callback, '/')); 379 | $extension = substr(strrchr($path, "."), 1); 380 | $index = isset($this->request['collection_index']) ? $this->request['collection_index'] : 0; 381 | $viewDir = is_array($viewDir = $this->router->collection->getRoutes('view_dir_' . $index)) ? $viewDir : [$viewDir]; 382 | $target = null; 383 | if (in_array('.' . $extension, $this->router->getConfig()['templateExtension'])){ 384 | foreach ($viewDir as $dir) { 385 | if (is_file($fullPath = rtrim($dir, '/') . '/' . $path) || is_file($fullPath = $path)) $target = $fullPath; 386 | } 387 | } 388 | if(is_null($target)){ 389 | foreach ($viewDir as $dir) { 390 | foreach ($this->router->getConfig()['templateExtension'] as $ext) { 391 | if (is_file($fullPath = rtrim($dir, '/') . '/' . $path . $ext) || is_file($fullPath = $path . $ext)) { 392 | $target = $fullPath; 393 | $extension = substr(strrchr($ext, "."), 1); 394 | break; 395 | } 396 | } 397 | } 398 | } 399 | return [ 400 | 'dispatcher' => $this->dispatcher['isTemplate'], 401 | 'template' => $target, 402 | 'extension' => $extension, 403 | 'callback' => $this->router->getConfig()['templateCallback'] 404 | ]; 405 | } 406 | return false; 407 | } 408 | 409 | } 410 | -------------------------------------------------------------------------------- /src/Routing/Matcher/MatcherInterface.php: -------------------------------------------------------------------------------- 1 | TemplateDispatcher::class, 36 | 'isController' => ControllerDispatcher::class, 37 | 'isControllerAndTemplate' => [ControllerDispatcher::class, TemplateDispatcher::class], 38 | ]; 39 | 40 | /** 41 | * @param Router $router 42 | */ 43 | public function __construct(Router $router) 44 | { 45 | $this->router = $router; 46 | } 47 | 48 | /** 49 | * @param array $resolver 50 | */ 51 | public function setResolver($resolver = []) 52 | { 53 | $this->resolver = $resolver; 54 | } 55 | 56 | /** 57 | * @param string $resolver 58 | */ 59 | public function addResolver($resolver) 60 | { 61 | $this->resolver[] = $resolver; 62 | } 63 | 64 | /** 65 | * @return array 66 | */ 67 | public function getResolver() 68 | { 69 | return $this->resolver; 70 | } 71 | 72 | /** 73 | * @param array $dispatcher 74 | */ 75 | public function setDispatcher($dispatcher = []) 76 | { 77 | $this->dispatcher = $dispatcher; 78 | } 79 | 80 | /** 81 | * @param $method 82 | * @param $class 83 | */ 84 | public function addDispatcher($method, $class) 85 | { 86 | $this->dispatcher[$method] = $class; 87 | } 88 | 89 | /** 90 | * @return bool 91 | */ 92 | public function match() 93 | { 94 | foreach ($this->resolver as $resolver) { 95 | if (is_array($target = call_user_func([$this, $resolver]))) { 96 | $this->setTarget($target); 97 | return true; 98 | } 99 | } 100 | return false; 101 | } 102 | 103 | /** 104 | * @param array $target 105 | */ 106 | public function setTarget($target = []) 107 | { 108 | $index = isset($this->request['collection_index']) ? $this->request['collection_index'] : 0; 109 | $this->router->route->setDetail($this->request); 110 | $this->router->route->setTarget($target); 111 | $this->router->route->addTarget('block', $this->router->collection->getRoutes('block_' . $index)); 112 | $this->router->route->addTarget('view_dir', $this->router->collection->getRoutes('view_dir_' . $index)); 113 | $this->router->route->addTarget('params', $this->router->collection->getRoutes('params_' . $index)); 114 | } 115 | 116 | /** 117 | * @return array|bool 118 | */ 119 | public function isControllerAndTemplate() 120 | { 121 | if (is_array($ctrl = $this->isController())) { 122 | if (is_array($tpl = $this->isTemplate())) { 123 | return array_merge(array_merge($ctrl, $tpl), [ 124 | 'dispatcher' => $this->dispatcher['isControllerAndTemplate'] 125 | ]); 126 | } 127 | return $ctrl; 128 | } 129 | return $this->isTemplate(); 130 | } 131 | 132 | /** 133 | * @return bool|array 134 | */ 135 | public function isTemplate() 136 | { 137 | foreach ($this->router->getConfig()['templateExtension'] as $extension) { 138 | for ($i = 0; $i < $this->router->collection->countRoutes; ++$i) { 139 | $url = explode('/', str_replace($this->router->collection->getRoutes('prefix_' . $i), '', $this->router->route->getUrl())); 140 | $end = array_pop($url); 141 | $url = implode('/', array_map('ucwords', $url)) . '/' . $end; 142 | $viewDir = is_array($viewDir = $this->router->collection->getRoutes('view_dir_' . $i)) ? $viewDir : [$viewDir]; 143 | foreach ($viewDir as $dir) { 144 | if (is_file(($template = rtrim($dir, '/') . $url . $extension))) { 145 | $this->request['collection_index'] = $i; 146 | return [ 147 | 'dispatcher' => $this->dispatcher['isTemplate'], 148 | 'template' => $template, 149 | 'extension' => substr(strrchr($extension, "."), 1), 150 | 'callback' => $this->router->getConfig()['templateCallback'] 151 | ]; 152 | } 153 | } 154 | } 155 | } 156 | return false; 157 | } 158 | 159 | /** 160 | * @return bool|array 161 | */ 162 | public function isController() 163 | { 164 | $routes = array_slice(explode('/', $this->router->route->getUrl()), 1); 165 | $i = 0; 166 | do { 167 | $route = ('/' . $routes[0] == $this->router->collection->getRoutes('prefix_' . $i)) ? array_slice($routes, 1) : $routes; 168 | if (isset($route[0])) { 169 | $class = (class_exists($this->router->collection->getRoutes('ctrl_namespace_' . $i) . ucfirst($route[0]) . 'Controller')) 170 | ? $this->router->collection->getRoutes('ctrl_namespace_' . $i) . ucfirst($route[0]) . 'Controller' 171 | : ucfirst($route[0]) . 'Controller'; 172 | $route[1] = isset($route[1]) ? $route[1] : 'index'; 173 | if (method_exists($class, $route[1])) { 174 | $this->request['parameters'] = array_slice($route, 2); 175 | $this->request['collection_index'] = $i; 176 | return [ 177 | 'dispatcher' => $this->dispatcher['isController'], 178 | 'di' => $this->router->getConfig()['di'], 179 | 'controller' => $class, 180 | 'action' => $route[1] 181 | ]; 182 | } 183 | } 184 | ++$i; 185 | } while ($i < $this->router->collection->countRoutes); 186 | return false; 187 | } 188 | 189 | } 190 | -------------------------------------------------------------------------------- /src/Routing/Middleware.php: -------------------------------------------------------------------------------- 1 | router = $router; 45 | } 46 | 47 | /** 48 | * @return array 49 | */ 50 | public function getMiddleware() 51 | { 52 | return $this->middleware; 53 | } 54 | 55 | /** 56 | * @param $action 57 | * @param $middleware 58 | * @return mixed|void 59 | */ 60 | public function setCallbackAction($action, $middleware) 61 | { 62 | $this->setMiddleware($action, $middleware); 63 | } 64 | 65 | /** 66 | * @param $action 67 | * @param $middleware 68 | */ 69 | private function setMiddleware($action, $middleware) 70 | { 71 | if (is_string($middleware)) { 72 | $middleware = rtrim($middleware, '/'); 73 | } 74 | if (is_array($middleware)) { 75 | $this->middleware[$action] = $middleware; 76 | } elseif (is_file($middleware) && is_array($mid = include $middleware)) { 77 | $this->middleware[$action] = $mid; 78 | } else { 79 | throw new \InvalidArgumentException('Accepted argument for setMiddleware are array and array file'); 80 | } 81 | } 82 | 83 | /** 84 | * @param $action 85 | * @return Router 86 | */ 87 | public function getCallbacks($action) 88 | { 89 | return $action == 'after' ? array_reverse($this->callbacks) : $this->callbacks; 90 | } 91 | 92 | /** 93 | * @description global middleware 94 | * @param $action 95 | */ 96 | public function globalMiddleware($action) 97 | { 98 | if (isset($this->middleware[$action]['global_middleware'])) { 99 | $this->callHandlers($this->middleware[$action]['global_middleware']); 100 | } 101 | } 102 | 103 | /** 104 | * @description block middleware 105 | * @param $action 106 | */ 107 | public function blockMiddleware($action) 108 | { 109 | if (isset($this->middleware[$action]['block_middleware'])) { 110 | if (isset($this->middleware[$action]['block_middleware'][$this->router->route->getTarget('block')])) { 111 | $blocks = $this->middleware[$action]['block_middleware'][$this->router->route->getTarget('block')]; 112 | $this->callHandlers($blocks); 113 | } 114 | } 115 | } 116 | 117 | /** 118 | * @description controller middleware 119 | * @param $action 120 | */ 121 | public function classMiddleware($action) 122 | { 123 | if (isset($this->middleware[$action]['class_middleware'])) { 124 | $ctrl = str_replace('\\', '/', $this->router->route->getTarget('controller')); 125 | if (isset($this->middleware[$action]['class_middleware'][$ctrl]) && class_exists($this->router->route->getTarget('controller'))) { 126 | $classes = $this->middleware[$action]['class_middleware'][$ctrl]; 127 | $this->callHandlers($classes); 128 | } 129 | } 130 | } 131 | 132 | /** 133 | * @description route middleware 134 | * @param $action 135 | */ 136 | public function routeMiddleware($action) 137 | { 138 | if (isset($this->middleware[$action]['route_middleware'])) { 139 | if (isset($this->router->route->getPath()['middleware']) && class_exists($this->middleware[$action]['route_middleware'][$this->router->route->getPath()['middleware']])) { 140 | $classes = $this->middleware[$action]['route_middleware'][$this->router->route->getPath()['middleware']]; 141 | $this->callHandlers($classes); 142 | } 143 | } 144 | } 145 | 146 | /** 147 | * @param $handlers 148 | * @param array $params 149 | */ 150 | private function callHandlers($handlers, $params = []){ 151 | $handlers = is_array($handlers) ? $handlers : [$handlers]; 152 | foreach ($handlers as $handler) { 153 | if($this->next && $this->handle($handler, $params) !== true){ 154 | break; 155 | } 156 | } 157 | } 158 | 159 | /** 160 | * @param $callback 161 | * @param array $params 162 | * @return mixed 163 | */ 164 | private function handle($callback, $params = []) 165 | { 166 | $callback = explode('@', $callback); 167 | $response = true; 168 | $method = isset($callback[1]) ? $callback[1] : 'handle'; 169 | if (class_exists($callback[0])) { 170 | $instance = call_user_func($this->router->getConfig()['di'], $callback[0]); 171 | if (method_exists($instance, $method)) { 172 | $reflectionMethod = new ReflectionMethod($instance, $method); 173 | $dependencies = $params; 174 | foreach ($reflectionMethod->getParameters() as $arg) { 175 | if (!is_null($arg->getClass())) { 176 | $dependencies[] = $this->getClass($arg->getClass()->name); 177 | } 178 | } 179 | $dependencies = array_merge($dependencies, [$this->router->route]); 180 | $response = $reflectionMethod->invokeArgs($instance, $dependencies); 181 | if(is_array($response) && isset($response['call'])){ 182 | if(isset($response['response']) && $response['response'] instanceof ResponseInterface){ 183 | $this->router->response = $response['response']; 184 | } 185 | $params = isset($response['params']) ? $response['params']: []; 186 | $this->callHandlers($response['call'], $params); 187 | $this->next = isset($response['next']) ? (bool)$response['next'] : false; 188 | } else if ($response instanceof ResponseInterface) { 189 | $this->router->response = $response; 190 | } 191 | } 192 | } 193 | return $response; 194 | } 195 | 196 | /** 197 | * @param $class 198 | * @return Route|RouteCollection|Router|mixed 199 | */ 200 | private function getClass($class) 201 | { 202 | switch ($class) { 203 | case Route::class: 204 | return $this->router->route; 205 | case Router::class: 206 | return $this->router; 207 | case RouteCollection::class: 208 | return $this->router->collection; 209 | case ResponseInterface::class: 210 | return $this->router->response; 211 | default: 212 | return call_user_func_array($this->router->getConfig()['di'], [$class]); 213 | } 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /src/Routing/MiddlewareInterface.php: -------------------------------------------------------------------------------- 1 | 'Continue', 55 | 101 => 'Switching Protocols', 56 | 102 => 'Processing', // RFC2518 57 | 200 => 'OK', 58 | 201 => 'Created', 59 | 202 => 'Accepted', 60 | 203 => 'Non-Authoritative Information', 61 | 204 => 'No Content', 62 | 205 => 'Reset Content', 63 | 206 => 'Partial Content', 64 | 207 => 'Multi-Status', // RFC4918 65 | 208 => 'Already Reported', // RFC5842 66 | 226 => 'IM Used', // RFC3229 67 | 300 => 'Multiple Choices', 68 | 301 => 'Moved Permanently', 69 | 302 => 'Found', 70 | 303 => 'See Other', 71 | 304 => 'Not Modified', 72 | 305 => 'Use Proxy', 73 | 307 => 'Temporary Redirect', 74 | 308 => 'Permanent Redirect', // RFC7238 75 | 400 => 'Bad Request', 76 | 401 => 'Unauthorized', 77 | 402 => 'Payment Required', 78 | 403 => 'Forbidden', 79 | 404 => 'Not Found', 80 | 405 => 'Method Not Allowed', 81 | 406 => 'Not Acceptable', 82 | 407 => 'Proxy Authentication Required', 83 | 408 => 'Request Timeout', 84 | 409 => 'Conflict', 85 | 410 => 'Gone', 86 | 411 => 'Length Required', 87 | 412 => 'Precondition Failed', 88 | 413 => 'Payload Too Large', 89 | 414 => 'URI Too Long', 90 | 415 => 'Unsupported Media Type', 91 | 416 => 'Range Not Satisfiable', 92 | 417 => 'Expectation Failed', 93 | 418 => 'I\'m a teapot', // RFC2324 94 | 422 => 'Unprocessable Entity', // RFC4918 95 | 423 => 'Locked', // RFC4918 96 | 424 => 'Failed Dependency', // RFC4918 97 | 425 => 'Reserved for WebDAV advanced collections expired proposal', // RFC2817 98 | 426 => 'Upgrade Required', // RFC2817 99 | 428 => 'Precondition Required', // RFC6585 100 | 429 => 'Too Many Requests', // RFC6585 101 | 431 => 'Request Header Fields Too Large', // RFC6585 102 | 500 => 'Internal Server Error', 103 | 501 => 'Not Implemented', 104 | 502 => 'Bad Gateway', 105 | 503 => 'Service Unavailable', 106 | 504 => 'Gateway Timeout', 107 | 505 => 'HTTP Version Not Supported', 108 | 506 => 'Variant Also Negotiates (Experimental)', // RFC2295 109 | 507 => 'Insufficient Storage', // RFC4918 110 | 508 => 'Loop Detected', // RFC5842 111 | 510 => 'Not Extended', // RFC2774 112 | 511 => 'Network Authentication Required', // RFC6585 113 | ); 114 | 115 | /** 116 | * Constructor. 117 | * 118 | * @param mixed $content The response content, see setContent() 119 | * @param int $status The response status code 120 | * @param array $headers An array of response headers 121 | * 122 | * @throws \InvalidArgumentException When the HTTP status code is not valid 123 | */ 124 | public function __construct($content = '', $status = 200, $headers = array()) 125 | { 126 | $this->headers = $headers; 127 | $this->setContent($content); 128 | $this->setStatusCode($status); 129 | $this->setProtocolVersion('1.0'); 130 | } 131 | 132 | 133 | /** 134 | * Returns the Response as an HTTP string. 135 | * 136 | * The string representation of the Response is the same as the 137 | * one that will be sent to the client only if the prepare() method 138 | * has been called before. 139 | * 140 | * @return string The Response as an HTTP string 141 | * 142 | * @see prepare() 143 | */ 144 | public function __toString() 145 | { 146 | return 147 | sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText)."\r\n". 148 | $this->headers."\r\n". 149 | $this->getContent(); 150 | } 151 | 152 | /** 153 | * Sends content for the current web response. 154 | * 155 | * @return Response 156 | */ 157 | public function sendContent() 158 | { 159 | echo $this->content; 160 | 161 | return $this; 162 | } 163 | 164 | /** 165 | * @return $this 166 | */ 167 | public function sendHeaders() 168 | { 169 | foreach($this->headers as $key => $content) 170 | header($key.' : '.$content); 171 | http_response_code($this->getStatusCode()); 172 | return $this; 173 | } 174 | 175 | /** 176 | * Sends HTTP headers and content. 177 | * 178 | * @return Response 179 | */ 180 | public function send() 181 | { 182 | $this->sendHeaders(); 183 | $this->sendContent(); 184 | 185 | if (function_exists('fastcgi_finish_request')) { 186 | fastcgi_finish_request(); 187 | } 188 | 189 | return $this; 190 | } 191 | 192 | /** 193 | * @param array $headers 194 | */ 195 | public function setHeaders($headers = []) 196 | { 197 | $this->headers = $headers; 198 | } 199 | /** 200 | * Sets the response content. 201 | * 202 | * Valid types are strings, numbers, null, and objects that implement a __toString() method. 203 | * 204 | * @param mixed $content Content that can be cast to string 205 | * 206 | * @return Response 207 | * 208 | * @throws \UnexpectedValueException 209 | */ 210 | public function setContent($content) 211 | { 212 | if (null !== $content && !is_string($content) && !is_numeric($content) && !is_callable(array($content, '__toString'))) { 213 | throw new \UnexpectedValueException(sprintf('The Response content must be a string or object implementing __toString(), "%s" given.', gettype($content))); 214 | } 215 | 216 | $this->content = (string) $content; 217 | 218 | return $this; 219 | } 220 | 221 | /** 222 | * Gets the current response content. 223 | * 224 | * @return string Content 225 | */ 226 | public function getContent() 227 | { 228 | return $this->content; 229 | } 230 | 231 | /** 232 | * Sets the HTTP protocol version (1.0 or 1.1). 233 | * 234 | * @param string $version The HTTP protocol version 235 | * 236 | * @return Response 237 | */ 238 | public function setProtocolVersion($version) 239 | { 240 | $this->version = $version; 241 | 242 | return $this; 243 | } 244 | 245 | /** 246 | * Gets the HTTP protocol version. 247 | * 248 | * @return string The HTTP protocol version 249 | */ 250 | public function getProtocolVersion() 251 | { 252 | return $this->version; 253 | } 254 | 255 | /** 256 | * Sets the response status code. 257 | * 258 | * @param int $code HTTP status code 259 | * @param mixed $text HTTP status text 260 | * 261 | * If the status text is null it will be automatically populated for the known 262 | * status codes and left empty otherwise. 263 | * 264 | * @return Response 265 | * 266 | * @throws \InvalidArgumentException When the HTTP status code is not valid 267 | */ 268 | public function setStatusCode($code, $text = null) 269 | { 270 | $this->statusCode = $code = (int) $code; 271 | if ($this->isInvalid()) { 272 | throw new \InvalidArgumentException(sprintf('The HTTP status code "%s" is not valid.', $code)); 273 | } 274 | 275 | if (null === $text) { 276 | $this->statusText = isset(self::$statusTexts[$code]) ? self::$statusTexts[$code] : 'unknown status'; 277 | 278 | return $this; 279 | } 280 | 281 | if (false === $text) { 282 | $this->statusText = ''; 283 | 284 | return $this; 285 | } 286 | 287 | $this->statusText = $text; 288 | 289 | return $this; 290 | } 291 | 292 | /** 293 | * Retrieves the status code for the current web response. 294 | * 295 | * @return int Status code 296 | */ 297 | public function getStatusCode() 298 | { 299 | return $this->statusCode; 300 | } 301 | 302 | /** 303 | * Sets the response charset. 304 | * 305 | * @param string $charset Character set 306 | * 307 | * @return Response 308 | */ 309 | public function setCharset($charset) 310 | { 311 | $this->charset = $charset; 312 | 313 | return $this; 314 | } 315 | 316 | /** 317 | * Retrieves the response charset. 318 | * 319 | * @return string Character set 320 | */ 321 | public function getCharset() 322 | { 323 | return $this->charset; 324 | } 325 | 326 | 327 | // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html 328 | /** 329 | * Is response invalid? 330 | * 331 | * @return bool 332 | */ 333 | public function isInvalid() 334 | { 335 | return $this->statusCode < 100 || $this->statusCode >= 600; 336 | } 337 | 338 | /** 339 | * Is response informative? 340 | * 341 | * @return bool 342 | */ 343 | public function isInformational() 344 | { 345 | return $this->statusCode >= 100 && $this->statusCode < 200; 346 | } 347 | 348 | /** 349 | * Is response successful? 350 | * 351 | * @return bool 352 | */ 353 | public function isSuccessful() 354 | { 355 | return $this->statusCode >= 200 && $this->statusCode < 300; 356 | } 357 | 358 | /** 359 | * Is the response a redirect? 360 | * 361 | * @return bool 362 | */ 363 | public function isRedirection() 364 | { 365 | return $this->statusCode >= 300 && $this->statusCode < 400; 366 | } 367 | 368 | /** 369 | * Is there a client error? 370 | * 371 | * @return bool 372 | */ 373 | public function isClientError() 374 | { 375 | return $this->statusCode >= 400 && $this->statusCode < 500; 376 | } 377 | 378 | /** 379 | * Was there a server side error? 380 | * 381 | * @return bool 382 | */ 383 | public function isServerError() 384 | { 385 | return $this->statusCode >= 500 && $this->statusCode < 600; 386 | } 387 | 388 | /** 389 | * Is the response OK? 390 | * 391 | * @return bool 392 | */ 393 | public function isOk() 394 | { 395 | return 200 === $this->statusCode; 396 | } 397 | 398 | /** 399 | * Is the response forbidden? 400 | * 401 | * @return bool 402 | */ 403 | public function isForbidden() 404 | { 405 | return 403 === $this->statusCode; 406 | } 407 | 408 | /** 409 | * Is the response a not found error? 410 | * 411 | * @return bool 412 | */ 413 | public function isNotFound() 414 | { 415 | return 404 === $this->statusCode; 416 | } 417 | 418 | /** 419 | * Is the response a redirect of some form? 420 | * 421 | * @param string $location 422 | * 423 | * @return bool 424 | */ 425 | public function isRedirect($location = null) 426 | { 427 | return in_array($this->statusCode, array(201, 301, 302, 303, 307, 308)) && (null === $location ?: $location == $this->headers->get('Location')); 428 | } 429 | 430 | /** 431 | * Is the response empty? 432 | * 433 | * @return bool 434 | */ 435 | public function isEmpty() 436 | { 437 | return in_array($this->statusCode, array(204, 304)); 438 | } 439 | 440 | } 441 | -------------------------------------------------------------------------------- /src/Routing/ResponseInterface.php: -------------------------------------------------------------------------------- 1 | method = (isset($_SERVER['REQUEST_METHOD'])) ? $_SERVER['REQUEST_METHOD'] : 'GET'; 47 | if ($this->method == 'POST' && isset($_SERVER['X-HTTP-METHOD-OVERRIDE'])) { 48 | $this->method = strtoupper($_SERVER['X-HTTP-METHOD-OVERRIDE']); 49 | } 50 | if (isset($_POST['_METHOD']) && in_array($_POST['_METHOD'], array('PUT', 'PATCH', 'HEAD', 'DELETE'))) { 51 | $this->method = $_POST['_METHOD']; 52 | } 53 | } 54 | 55 | /** 56 | * @param array $args 57 | */ 58 | public function set($args = []) 59 | { 60 | if (isset($args['name'])) $this->name = $args['name']; 61 | if (isset($args['callback'])) $this->callback = $args['callback']; 62 | if (isset($args['target'])) $this->target = $args['target']; 63 | if (isset($args['detail'])) $this->detail = $args['detail']; 64 | } 65 | 66 | /** 67 | * @return null 68 | */ 69 | public function getUrl() 70 | { 71 | return $this->url; 72 | } 73 | 74 | /** 75 | * @param $url 76 | */ 77 | public function setUrl($url) 78 | { 79 | $this->url = $url; 80 | } 81 | 82 | /** 83 | * @return null 84 | */ 85 | public function getName() 86 | { 87 | return $this->name; 88 | } 89 | 90 | /** 91 | * @param $name 92 | */ 93 | public function setName($name) 94 | { 95 | $this->name = $name; 96 | } 97 | 98 | /** 99 | * @return mixed 100 | */ 101 | public function getCallback() 102 | { 103 | return $this->callback; 104 | } 105 | 106 | /** 107 | * @param $callback 108 | */ 109 | public function setCallback($callback) 110 | { 111 | $this->callback = $callback; 112 | } 113 | 114 | /** 115 | * @return array 116 | */ 117 | public function getMethod() 118 | { 119 | return $this->method; 120 | } 121 | 122 | /** 123 | * @return array 124 | */ 125 | public function getDetail() 126 | { 127 | return $this->detail; 128 | } 129 | 130 | /** 131 | * @param $detail 132 | */ 133 | public function setDetail($detail) 134 | { 135 | $this->detail = array_merge($detail, $this->detail); 136 | } 137 | 138 | /** 139 | * @param $key 140 | * @param $value 141 | */ 142 | public function addDetail($key, $value) 143 | { 144 | $this->detail[$key] = $value; 145 | } 146 | 147 | /** 148 | * @param null $key 149 | * @return array|string 150 | */ 151 | public function getTarget($key = null) 152 | { 153 | if (!is_null($key)) 154 | return isset($this->target[$key]) ? $this->target[$key] : ''; 155 | return empty($this->target) ? '' : $this->target; 156 | } 157 | 158 | /** 159 | * @param $target 160 | */ 161 | public function setTarget($target = []) 162 | { 163 | $this->target = $target; 164 | } 165 | 166 | /** 167 | * @param $key 168 | * @param $value 169 | */ 170 | public function addTarget($key, $value) 171 | { 172 | $this->target[$key] = $value; 173 | } 174 | 175 | /** 176 | * @param null $key 177 | * @return bool 178 | */ 179 | public function hasTarget($key = null) 180 | { 181 | if (!is_null($key)) 182 | return isset($this->target[$key]) ? true : false; 183 | return empty($this->target) ? false : true; 184 | } 185 | 186 | /** 187 | * @return array 188 | */ 189 | public function getData() 190 | { 191 | return (isset($this->getDetail()['data']) && is_array($this->getDetail()['data'])) ? $this->getDetail()['data'] : []; 192 | } 193 | 194 | /** 195 | * @param $name 196 | * @param $arguments 197 | * @return null 198 | */ 199 | public function __call($name, $arguments) 200 | { 201 | if (substr($name, 0, 3) === "get") { 202 | $key = strtolower(str_replace('get', '', $name)); 203 | return isset($this->detail[$key]) ? $this->detail[$key] : ''; 204 | } elseif (substr($name, 0, 3) === "set") { 205 | $key = strtolower(str_replace('set', '', $name)); 206 | $this->detail[$key] = $arguments[0]; 207 | } 208 | return ''; 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /src/Routing/RouteCollection.php: -------------------------------------------------------------------------------- 1 | addRoutes($routes, $options); 37 | } 38 | 39 | /** 40 | * @param array|string $routes 41 | * @param array $options 42 | */ 43 | public function addRoutes($routes = null, $options = []) 44 | { 45 | if (!is_null($routes) && !is_array($routes)) { 46 | if (strpos($routes, '.php') === false) $routes = trim($routes, '/') . '/'; 47 | if (is_file($routes . '/routes.php') && is_array($routesFile = include $routes . '/routes.php')) $routes = $routesFile; 48 | elseif (is_file($routes) && is_array($routesFile = include $routes)) $routes = $routesFile; 49 | else throw new \InvalidArgumentException('Argument for "' . get_called_class() . '" constructor is not recognized. Expected argument array or file containing array but "' . $routes . '" given'); 50 | } 51 | $options['routes'] = is_array($routes) ? $routes : []; 52 | $this->setRoutes($options, $this->countRoutes); 53 | $this->countRoutes++; 54 | } 55 | 56 | /** 57 | * @param null $key 58 | * @return array 59 | */ 60 | public function getRoutes($key = null) 61 | { 62 | return (!is_null($key)) 63 | ? isset($this->routes[$key]) ? $this->routes[$key] : '' 64 | : $this->routes; 65 | } 66 | 67 | /** 68 | * @param $args 69 | */ 70 | public function setPrefix($args) 71 | { 72 | if (is_array($args)) { 73 | $nbrArgs = count($args); 74 | for ($i = 0; $i < $nbrArgs; ++$i) 75 | $this->routes['prefix_' . $i] = '/' . trim($args[$i], '/'); 76 | } elseif (is_string($args)) 77 | for ($i = 0; $i < $this->countRoutes; ++$i) 78 | $this->routes['prefix_' . $i] = '/' . trim($args, '/'); 79 | if ($this->countRoutes == 0) $this->countRoutes++; 80 | } 81 | 82 | /** 83 | * @param $args 84 | */ 85 | public function setOption($args = []) 86 | { 87 | $nbrArgs = count($args); 88 | for ($i = 0; $i < $nbrArgs; ++$i) { 89 | if (is_array($args[$i])) { 90 | $this->setRoutes($args[$i], $i); 91 | if (!isset($this->routes['routes_' . $i])) $this->routes['routes_' . $i] = []; 92 | } 93 | } 94 | if ($this->countRoutes == 0) $this->countRoutes++; 95 | } 96 | 97 | /** 98 | * @param array $args 99 | * @param $i 100 | */ 101 | public function setRoutes($args = [], $i) 102 | { 103 | $this->routes['routes_' . $i] = $args['routes']; 104 | $this->routes['block_' . $i] = (isset($args['block']) && !empty($args['block'])) ? rtrim($args['block'], '/') . '/' : ''; 105 | $this->routes['view_dir_' . $i] = (isset($args['view_dir']) && !empty($args['view_dir'])) ? $args['view_dir'] : []; 106 | $this->routes['ctrl_namespace_' . $i] = (isset($args['ctrl_namespace']) && !empty($args['ctrl_namespace'])) ? trim($args['ctrl_namespace'], '\\') . '\\' : ''; 107 | $this->routes['prefix_' . $i] = (isset($args['prefix']) && !empty($args['prefix'])) ? '/' . trim($args['prefix'], '/') : ''; 108 | $this->routes['subdomain_' . $i] = (isset($args['subdomain'])) ? $args['subdomain'] : ''; 109 | $this->routes['protocol_' . $i] = (isset($args['protocol'])) ? $args['protocol'] : 'http'; 110 | $this->routes['params_' . $i] = (isset($args['params'])) ? $args['params'] : []; 111 | } 112 | 113 | /** 114 | * @param string $root 115 | * @param string $script_file 116 | * @return bool 117 | */ 118 | public function generateRoutesPath($root = null, $script_file = 'index.php') 119 | { 120 | $protocol = isset($_SERVER['REQUEST_SCHEME']) ? $_SERVER['REQUEST_SCHEME'] : 'http'; 121 | $domain = isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : null; 122 | 123 | if(!is_null($root)){ 124 | $protocol = explode('://', $root); 125 | $protocol = $protocol[0]; 126 | }else{ 127 | $root = $protocol . '://' . $domain . ((!empty($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] != 80) ? ':' . $_SERVER['SERVER_PORT'] : '') . str_replace('/' . $script_file, '', $_SERVER['SCRIPT_NAME']); 128 | } 129 | 130 | $new_domain = $this->getDomain($root); 131 | if (!is_null($domain) && strpos($domain, $new_domain) !== false) { 132 | $root = str_replace($domain, $new_domain, $root); 133 | } 134 | 135 | $count = 0; 136 | for ($i = 0; $i < $this->countRoutes; ++$i) { 137 | $prefix = (isset($this->routes['prefix_' . $i])) ? $this->routes['prefix_' . $i] : ''; 138 | $subdomain = (isset($this->routes['subdomain_' . $i])) ? $this->routes['subdomain_' . $i] : ''; 139 | $block_protocol = (isset($this->routes['protocol_' . $i])) ? $this->routes['protocol_' . $i] : 'http'; 140 | $url = (!empty($subdomain)) ? str_replace($protocol . '://', $block_protocol . '://' . $subdomain . '.', $root) : $root; 141 | if (isset($this->routes['routes_' . $i])) 142 | foreach ($this->routes['routes_' . $i] as $route => $dependencies) { 143 | if (is_array($dependencies) && isset($dependencies['use']) && !is_array($dependencies['use'])) { 144 | $use = (is_callable($dependencies['use'])) ? 'closure-' . $count : trim($dependencies['use'], '/'); 145 | } elseif (!is_array($dependencies)) { 146 | $use = (is_callable($dependencies)) ? 'closure-' . $count : trim($dependencies, '/'); 147 | } else { 148 | $use = $route; 149 | } 150 | if (isset($route[0]) && $route[0] == '/') { 151 | $full_url = rtrim($url, '/') . '/' . trim($prefix, '/') . (empty($prefix) ? '' : '/') . trim($route, '/'); 152 | (!is_callable($dependencies) && isset($dependencies['name'])) 153 | ? $this->routesByName[$use . '#' . $dependencies['name']] = $full_url 154 | : $this->routesByName[$use] = $full_url; 155 | } else { 156 | $full_url = $block_protocol . '://' . str_replace('{host}', $new_domain, $route); 157 | (!is_callable($dependencies) && isset($dependencies['name'])) 158 | ? $this->routesByName[$use . '#' . $dependencies['name']] = $full_url . $prefix 159 | : $this->routesByName[$use] = $full_url . $prefix; 160 | } 161 | $count++; 162 | } 163 | } 164 | return true; 165 | } 166 | 167 | /** 168 | * @param $url 169 | * @return string 170 | */ 171 | public function getDomain($url) 172 | { 173 | $url = parse_url($url); 174 | $domain = $url['host']; 175 | if (preg_match('/(?P[a-z0-9][a-z0-9\-]{1,63}\.[a-z\.]{2,6})$/i', $domain, $regs)) { 176 | return $regs['domain']; 177 | } 178 | return $domain; 179 | } 180 | 181 | /** 182 | * @param null $name 183 | * @param array $params 184 | * @return mixed 185 | */ 186 | public function getRoutePath($name, $params = []) 187 | { 188 | foreach ($this->routesByName as $key => $route) { 189 | $param = explode('#', $key); 190 | if(isset($params['{subdomain}'])) $route = str_replace('*', $params['{subdomain}'], $route); 191 | if(isset($params['{method}'])) $route = str_replace('*', $params['{method}'], $route); 192 | foreach ($params as $key2 => $value) $route = str_replace(':' . $key2, $value, $route); 193 | if ($param[0] == trim($name, '/')) return $route; 194 | else if (isset($param[1]) && $param[1] == $name) return $route; 195 | } 196 | return null; 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /src/Routing/Router.php: -------------------------------------------------------------------------------- 1 | ['.html', '.php', '.json', '.xml'], 41 | 'templateCallback' => [], 42 | 'di' => '', 43 | 'generateRoutesPath' => false, 44 | ]; 45 | 46 | /** 47 | * @param RouteCollection $collection 48 | * @param ResponseInterface $response 49 | * @param Route $route 50 | */ 51 | public function __construct(RouteCollection $collection, ResponseInterface $response = null, Route $route = null) 52 | { 53 | $this->collection = $collection; 54 | $this->response = is_null($response) ? new Response() : $response; 55 | $this->route = is_null($route) ? new Route() : $route; 56 | $this->config['di'] = function ($class) { 57 | return new $class; 58 | }; 59 | } 60 | 61 | /** 62 | * @param array $config 63 | */ 64 | public function setConfig($config) 65 | { 66 | $this->config = array_merge($this->config, $config); 67 | } 68 | 69 | /** 70 | * @return array 71 | */ 72 | public function getConfig() 73 | { 74 | return $this->config; 75 | } 76 | 77 | /** 78 | * @param object|array $middleware 79 | */ 80 | public function setMiddleware($middleware) 81 | { 82 | $this->middlewareCollection = is_array($middleware) 83 | ? $middleware 84 | : [$middleware]; 85 | } 86 | 87 | /** 88 | * @param MiddlewareInterface $middleware 89 | */ 90 | public function addMiddleware(MiddlewareInterface $middleware) 91 | { 92 | $this->middlewareCollection[] = $middleware; 93 | } 94 | 95 | /** 96 | * @param object|array $matcher 97 | */ 98 | public function setMatcher($matcher) 99 | { 100 | $this->matcher = is_array($matcher) 101 | ? $matcher 102 | : [$matcher]; 103 | } 104 | 105 | /** 106 | * @param string $matcher 107 | */ 108 | public function addMatcher($matcher) 109 | { 110 | $this->matcher[] = $matcher; 111 | } 112 | 113 | /** 114 | * @description main function 115 | */ 116 | public function run() 117 | { 118 | $this->setUrl(); 119 | if ($this->config['generateRoutesPath']) $this->collection->generateRoutesPath(); 120 | if ($this->match() === true) { 121 | $this->callMiddleware('before'); 122 | if (!in_array(substr($this->response->getStatusCode(), 0, 1), [3,4,5])) { 123 | $this->callTarget(); 124 | } 125 | }else{ 126 | $this->response->setStatusCode(404); 127 | } 128 | $this->callMiddleware('after'); 129 | return $this->response->send(); 130 | } 131 | 132 | /** 133 | * @description call the middleware before and after the target 134 | * @param $action 135 | */ 136 | public function callMiddleware($action) 137 | { 138 | foreach ($this->middlewareCollection as $middleware) { 139 | if ($middleware instanceof MiddlewareInterface) { 140 | foreach ($middleware->getCallbacks($action) as $callback) { 141 | if (method_exists($middleware, $callback)) { 142 | call_user_func_array([$middleware, $callback], [$action]); 143 | } 144 | } 145 | } 146 | } 147 | } 148 | 149 | /** 150 | * @param null $url 151 | */ 152 | public function setUrl($url = null) 153 | { 154 | if (is_null($url)) 155 | $url = (isset($_GET['url'])) ? $_GET['url'] : substr(str_replace(str_replace('/index.php', '', $_SERVER['SCRIPT_NAME']), '', $_SERVER['REQUEST_URI']), 1); 156 | $this->route->setUrl('/' . trim(explode('?', $url)[0], '/')); 157 | } 158 | 159 | /** 160 | * @return bool 161 | */ 162 | public function match() 163 | { 164 | foreach ($this->matcher as $key => $matcher) { 165 | if (call_user_func([$this->matcher[$key], 'match'])) return true; 166 | } 167 | return false; 168 | } 169 | 170 | /** 171 | * @description call the target for the request uri 172 | */ 173 | public function callTarget() 174 | { 175 | $target = is_array($this->route->getTarget('dispatcher')) ? $this->route->getTarget('dispatcher') : [$this->route->getTarget('dispatcher')]; 176 | if (!empty($target)) { 177 | foreach ($target as $call) { 178 | $this->dispatcher = new $call($this); 179 | call_user_func([$this->dispatcher, 'call']); 180 | } 181 | } 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /tests/app/Block1/Namespace1Controller.php: -------------------------------------------------------------------------------- 1 | 'JetFire']; 15 | } 16 | } -------------------------------------------------------------------------------- /tests/app/Block1/Views/Smart/index1.html: -------------------------------------------------------------------------------- 1 | Smart1 -------------------------------------------------------------------------------- /tests/app/Block1/Views/contact.php: -------------------------------------------------------------------------------- 1 | Contact1 -------------------------------------------------------------------------------- /tests/app/Block1/Views/index.html: -------------------------------------------------------------------------------- 1 | Hello1 -------------------------------------------------------------------------------- /tests/app/Block1/Views/log.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/app/Block1/Views/user.html: -------------------------------------------------------------------------------- 1 | User1 -------------------------------------------------------------------------------- /tests/app/Block1/routes.php: -------------------------------------------------------------------------------- 1 | 'index', 6 | 7 | '/user1-:id' => [ 8 | 'use' => 'user.html', 9 | 'arguments' => ['id' => '[0-9]+'] 10 | ], 11 | 12 | '/home1' => [ 13 | 'use' => 'JetFire\Routing\App\Block1\Namespace1Controller@index', 14 | ], 15 | 16 | '/home-:id' => [ 17 | 'use' => 'Namespace1Controller@index2', 18 | 'arguments' => ['id' => '[0-9]+'] 19 | ], 20 | 21 | '/contact1' => [ 22 | 'use' => 'Normal1Controller@contact', 23 | 'template' => 'contact.php', 24 | 'name' => 'contact' 25 | ], 26 | 27 | '/log' => [ 28 | 'use' => 'Normal1Controller@log', 29 | 'template' => 'log.php', 30 | 'name' => 'log' 31 | ], 32 | 33 | '/search1-:id-:name' => [ 34 | 'use' => function ($id, $name) { 35 | echo 'Search' . $id . $name; 36 | }, 37 | 'arguments' => ['id' => '[0-9]+', 'name' => '[a-z]*'], 38 | ], 39 | ]; -------------------------------------------------------------------------------- /tests/app/Block2/Controllers/Namespace2Controller.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/app/Block2/routes.php: -------------------------------------------------------------------------------- 1 | 'index.html', 6 | 7 | '/user2-:id' => [ 8 | 'use' => 'user.html', 9 | 'arguments' => ['id' => '[0-9]+'] 10 | ], 11 | 12 | '/log2' => [ 13 | 'use' => function(){ 14 | return ['name' => 'JetFire']; 15 | }, 16 | 'template' => 'log.php' 17 | ], 18 | 19 | '/home2' => [ 20 | 'use' => 'JetFire\Routing\App\Block2\Controllers\Namespace2Controller@index', 21 | ], 22 | 23 | '/home-:id' => [ 24 | 'use' => 'Namespace2Controller@index2', 25 | 'arguments' => ['id' => '[0-9]+'] 26 | ], 27 | 28 | '/contact2' => [ 29 | 'use' => 'Normal2Controller@contact', 30 | 'name' => 'contact' 31 | ], 32 | 33 | '/search2' => [ 34 | 'use' => 'Normal2Controller@search', 35 | 'method' => 'POST', 36 | 'name' => 'search' 37 | ], 38 | 39 | '/api/users' => [ 40 | 'use' => [ 41 | 'GET' => function($response){ 42 | $response->setStatusCode(500); 43 | } 44 | ] 45 | ], 46 | '/api/users/1' => [ 47 | 'use' => [ 48 | 'GET' => function($response){ 49 | $response->setHeaders(['Content-Type' => 'application/json']); 50 | return ['name' => 'Peter']; 51 | } 52 | ] 53 | ] 54 | ]; -------------------------------------------------------------------------------- /tests/app/Block2/user.html: -------------------------------------------------------------------------------- 1 | User2 -------------------------------------------------------------------------------- /tests/app/Config/middleware.inc.php: -------------------------------------------------------------------------------- 1 | [ 5 | 6 | ], 7 | 8 | 'block_middleware' => [ 9 | 10 | ], 11 | 12 | 'class_middleware' => [ 13 | 'JetFire/Routing/Test/Controllers/NormalController' => '', 14 | ], 15 | 16 | 'route_middleware' => [ 17 | 'home' => '' 18 | ], 19 | 20 | ]; -------------------------------------------------------------------------------- /tests/app/Config/routes.php: -------------------------------------------------------------------------------- 1 | 'index.html', 6 | 7 | '/user-:id' => [ 8 | 'use' => 'user.html', 9 | 'arguments' => ['id' => '[0-9]+'] 10 | ], 11 | 12 | '/home' => [ 13 | 'use' => 'JetFire\Routing\App\Controllers\NamespaceController@index', 14 | ], 15 | 16 | '/home-:id' => [ 17 | 'use' => 'NamespaceController@index2', 18 | 'arguments' => ['id' => '[0-9]+'] 19 | ], 20 | 21 | '/contact' => [ 22 | 'use' => 'NormalController@contact', 23 | 'name' => 'contact' 24 | ], 25 | 26 | '/search' => [ 27 | 'use' => 'NormalController@search', 28 | 'method' => 'POST', 29 | 'name' => 'search' 30 | ], 31 | 32 | '/log' => function(){ 33 | echo 'yes'; 34 | } 35 | ]; -------------------------------------------------------------------------------- /tests/app/Controllers/AppController.php: -------------------------------------------------------------------------------- 1 | addRoutes(ROOT . '/Config/routes.php', [ 30 | 'view_dir' => ROOT . '/Views', 31 | 'ctrl_namespace' => 'JetFire\Routing\App\Controllers', 32 | ]); 33 | $collection->addRoutes(ROOT . '/Block1/routes.php', [ 34 | 'view_dir' => ROOT . '/Block1/Views', 35 | 'ctrl_namespace' => 'JetFire\Routing\App\Block1', 36 | 'prefix' => 'block1' 37 | ]); 38 | $collection->addRoutes(ROOT . '/Block2/routes.php', [ 39 | 'view_dir' => ROOT . '/Block2/', 40 | 'ctrl_namespace' => 'JetFire\Routing\App\Block2\Controllers' 41 | ]); 42 | $_SERVER['REQUEST_METHOD'] = 'GET'; 43 | $router = new Router($collection); 44 | $router->addMatcher(new ArrayMatcher($router)); 45 | $router->addMatcher(new UriMatcher($router)); 46 | $this->middleware = new Middleware($router); 47 | } 48 | 49 | 50 | /** 51 | * @expectedException InvalidArgumentException 52 | */ 53 | public function testInvalidMiddleware(){ 54 | $this->middleware->setCallbackAction('before', '/Config/middleware.inc.php'); 55 | } 56 | 57 | /** 58 | * 59 | */ 60 | public function testMiddleware(){ 61 | $this->middleware->setCallbackAction('before', ROOT.'/Config/middleware.inc.php'); 62 | $this->assertTrue(is_array($this->middleware->getMiddleware()['before'])); 63 | } 64 | 65 | } -------------------------------------------------------------------------------- /tests/src/Routing/RouteCollectionTest.php: -------------------------------------------------------------------------------- 1 | collection = new RouteCollection(); 18 | } 19 | 20 | /** 21 | * @expectedException InvalidArgumentException 22 | */ 23 | public function testInvalidRoutes(){ 24 | $this->collection->addRoutes('routes.php'); 25 | } 26 | 27 | public function testCountRoutes(){ 28 | $this->collection->addRoutes(ROOT.'/Config/routes.php',['prefix'=>'/public']); 29 | $this->collection->addRoutes([ 30 | '/page-1' => 'index.html', 31 | '/page-2' => function(){ 32 | return 'Hello !'; 33 | } 34 | ]); 35 | $this->assertEquals(2,$this->collection->countRoutes); 36 | return $this->collection; 37 | } 38 | 39 | /** 40 | * @depends testCountRoutes 41 | * @param RouteCollection $collection 42 | */ 43 | public function testGetRoutes(RouteCollection $collection){ 44 | $this->assertArrayHasKey('view_dir_0',$collection->getRoutes()); 45 | $this->assertEquals('/public',$collection->getRoutes('prefix_0')); 46 | $this->assertEquals('',$collection->getRoutes('prefix_1')); 47 | } 48 | 49 | public function testSetPrefix(){ 50 | $this->collection->addRoutes(ROOT.'/Config/routes.php'); 51 | $this->collection->addRoutes([ 52 | '/page-1' => 'index.html', 53 | '/page-2' => function(){ 54 | return 'Hello !'; 55 | } 56 | ]); 57 | $this->collection->setPrefix('public'); 58 | $this->assertEquals('/public',$this->collection->getRoutes('prefix_0')); 59 | $this->assertEquals('/public',$this->collection->getRoutes('prefix_1')); 60 | $this->collection->setPrefix(['public','user']); 61 | $this->assertEquals('/public',$this->collection->getRoutes('prefix_0')); 62 | $this->assertEquals('/user',$this->collection->getRoutes('prefix_1')); 63 | } 64 | 65 | /** 66 | * @depends testCountRoutes 67 | * @param RouteCollection $collection 68 | * @return \JetFire\Routing\RouteCollection 69 | */ 70 | public function testGenerateRoutes(RouteCollection $collection){ 71 | $_SERVER['SERVER_NAME'] = 'localhost'; 72 | $_SERVER['SCRIPT_NAME'] = ''; 73 | $this->assertTrue($collection->generateRoutesPath()); 74 | return $collection; 75 | } 76 | 77 | /** 78 | * @depends testGenerateRoutes 79 | * @param RouteCollection $collection 80 | */ 81 | public function testGetPath(RouteCollection $collection){ 82 | $this->assertEquals('http://localhost/public/contact',$collection->getRoutePath('contact')); 83 | $this->assertNotEquals('http://localhost/contact',$collection->getRoutePath('search')); 84 | } 85 | } -------------------------------------------------------------------------------- /tests/src/Routing/RouterTest.php: -------------------------------------------------------------------------------- 1 | addRoutes(ROOT . '/Config/routes.php', [ 29 | 'view_dir' => ROOT . '/Views', 30 | 'ctrl_namespace' => 'JetFire\Routing\App\Controllers', 31 | ]); 32 | $collection->addRoutes(ROOT . '/Block1/routes.php', [ 33 | 'view_dir' => ROOT . '/Block1/Views', 34 | 'ctrl_namespace' => 'JetFire\Routing\App\Block1', 35 | 'prefix' => 'block1' 36 | ]); 37 | $collection->addRoutes(ROOT . '/Block2/routes.php', [ 38 | 'view_dir' => ROOT . '/Block2/', 39 | 'ctrl_namespace' => 'JetFire\Routing\App\Block2\Controllers' 40 | ]); 41 | $_SERVER['REQUEST_METHOD'] = 'GET'; 42 | $this->router = new Router($collection); 43 | $this->router->addMatcher(new ArrayMatcher($this->router)); 44 | $this->router->addMatcher(new UriMatcher($this->router)); 45 | } 46 | 47 | /** 48 | * @return array 49 | */ 50 | public function uriMatchWithoutRoutesProvider() 51 | { 52 | return array( 53 | array(ROOT . '/Views', 'JetFire\Routing\App\Controllers', '/app/index', 'Index', ''), 54 | array(ROOT . '/Views', 'JetFire\Routing\App\Controllers', '/smart/index', 'Smart', ''), 55 | array(ROOT . '/Block1/Views', 'JetFire\Routing\App\Block1', '/smart/index1', 'Smart1', ''), 56 | array(ROOT . '/Block1/Views', 'JetFire\Routing\App\Block1', '/block1/namespace1/index', 'Index1', 'block1'), 57 | array(ROOT . '/Block2', 'JetFire\Routing\App\Block2\Controllers', '/smart/index2', 'Smart2', ''), 58 | array(ROOT . '/Block2', 'JetFire\Routing\App\Block2\Controllers', '/normal2/contact', 'Contact2', ''), 59 | array(ROOT . '/Views', 'JetFire\Routing\App\Controllers', '/app/namespace/index', 'Index', 'app'), 60 | ); 61 | } 62 | 63 | /** 64 | * @dataProvider uriMatchWithoutRoutesProvider 65 | * @param $path 66 | * @param $namespace 67 | * @param $url 68 | * @param $output 69 | * @param $prefix 70 | */ 71 | public function testUriMatchWithoutRoutes($path, $namespace, $url, $output, $prefix) 72 | { 73 | $collection = new RouteCollection(null, [ 74 | 'view_dir' => $path, 75 | 'ctrl_namespace' => $namespace, 76 | 'prefix' => $prefix 77 | ]); 78 | $this->router = new Router($collection); 79 | $this->router->addMatcher(new UriMatcher($this->router)); 80 | $this->router->setUrl($url); 81 | $this->assertTrue($this->router->match()); 82 | $this->router->callTarget(); 83 | $this->router->response->sendContent(); 84 | $this->expectOutputString($output); 85 | } 86 | 87 | /** 88 | * @return array 89 | */ 90 | public function uriMatchTemplate() 91 | { 92 | return array( 93 | array('/smart/index', 'Smart'), 94 | array('/block1/smart/index1', 'Smart1'), 95 | array('/smart/index2', 'Smart2'), 96 | ); 97 | } 98 | 99 | /** 100 | * @dataProvider uriMatchTemplate 101 | * @param $url 102 | * @param $output 103 | */ 104 | public function testUriMatchTemplate($url, $output) 105 | { 106 | $this->router->setUrl($url); 107 | $this->assertTrue($this->router->match()); 108 | $this->router->callTarget(); 109 | $this->router->response->sendContent(); 110 | $this->expectOutputString($output); 111 | } 112 | 113 | /** 114 | * @return array 115 | */ 116 | public function uriMatchController() 117 | { 118 | return array( 119 | array('/normal/contact', 'Contact'), 120 | array('/block1/normal1/contact', 'Contact1'), 121 | array('/normal2/contact', 'Contact2'), 122 | array('/namespace/index', 'Index'), 123 | array('/namespace1/index', 'Index1'), 124 | array('/namespace2/index', 'Index2'), 125 | ); 126 | } 127 | 128 | /** 129 | * @dataProvider uriMatchController 130 | * @param $url 131 | * @param $output 132 | */ 133 | public function testUriMatchController($url, $output) 134 | { 135 | $this->router->setUrl($url); 136 | $this->assertTrue($this->router->match()); 137 | $this->router->callTarget(); 138 | $this->router->response->sendContent(); 139 | $this->expectOutputString($output); 140 | } 141 | 142 | /** 143 | * @return array 144 | */ 145 | public function matchTemplate() 146 | { 147 | return array( 148 | array('/index', 'Hello'), 149 | array('/block1/index1', 'Hello1'), 150 | array('/index2', 'Hello2'), 151 | array('/user-1', 'User'), 152 | array('/block1/user1-1', 'User1'), 153 | array('/user2-1', 'User2'), 154 | ); 155 | } 156 | 157 | /** 158 | * @dataProvider matchTemplate 159 | * @param $url 160 | * @param $output 161 | */ 162 | public function testMatchTemplate($url, $output) 163 | { 164 | $this->router->setUrl($url); 165 | $this->assertTrue($this->router->match()); 166 | $this->router->callTarget(); 167 | $this->router->response->sendContent(); 168 | $this->expectOutputString($output); 169 | } 170 | 171 | /** 172 | * @return array 173 | */ 174 | public function matchController() 175 | { 176 | return array( 177 | array('/home', 'Index'), 178 | array('/block1/home1', 'Index1'), 179 | array('/home2', 'Index2'), 180 | array('/home-1', 'Index1'), 181 | array('/block1/home-2', 'Index2'), 182 | array('/home-3', 'Index3'), 183 | array('/contact', 'Contact'), 184 | array('/block1/contact1', 'Contact1Contact1'), 185 | array('/block1/log', 'JetFire'), 186 | array('/log2', 'JetFire'), 187 | array('/contact2', 'Contact2'), 188 | ); 189 | } 190 | 191 | /** 192 | * @dataProvider matchController 193 | * @param $url 194 | * @param $output 195 | */ 196 | public function testMatchController($url, $output) 197 | { 198 | $this->router->setUrl($url); 199 | $this->assertTrue($this->router->match()); 200 | $this->router->callTarget(); 201 | $this->router->response->sendContent(); 202 | $this->expectOutputString($output); 203 | } 204 | 205 | /** 206 | * 207 | */ 208 | public function testPostResponseMethod() 209 | { 210 | $collection = new RouteCollection(); 211 | $collection->addRoutes(ROOT . '/Config/routes.php', [ 212 | 'view_dir' => ROOT . '/Views', 213 | 'ctrl_namespace' => 'JetFire\Routing\App\Controllers', 214 | ]); 215 | $_SERVER['REQUEST_METHOD'] = 'POST'; 216 | $router = new Router($collection); 217 | $router->addMatcher(new ArrayMatcher($router)); 218 | $router->setUrl('/search'); 219 | $this->assertTrue($router->match()); 220 | $router->callTarget(); 221 | $router->response->sendContent(); 222 | $this->assertEquals('POST', $router->route->getMethod()); 223 | } 224 | 225 | /** 226 | * 227 | */ 228 | public function testGetResponseMethod() 229 | { 230 | $collection = new RouteCollection(); 231 | $collection->addRoutes(ROOT . '/Config/routes.php', [ 232 | 'view_dir' => ROOT . '/Views', 233 | 'ctrl_namespace' => 'JetFire\Routing\App\Controllers', 234 | ]); 235 | $router = new Router($collection); 236 | $router->addMatcher(new ArrayMatcher($router)); 237 | $_SERVER['REQUEST_METHOD'] = 'GET'; 238 | $router->setUrl('/search'); 239 | $this->assertFalse($router->match()); 240 | $this->assertEquals(405, $router->response->getStatusCode()); 241 | } 242 | 243 | /** 244 | * 245 | */ 246 | public function testClosureWithParameters() 247 | { 248 | $this->router->setUrl('/block1/search1-3-peter'); 249 | $this->assertTrue($this->router->match()); 250 | $this->router->callTarget(); 251 | $this->router->response->sendContent(); 252 | $this->expectOutputString('Search3peter'); 253 | } 254 | 255 | /** 256 | * 257 | */ 258 | public function testClosure() 259 | { 260 | $this->router->setUrl('/log'); 261 | $this->assertTrue($this->router->match()); 262 | $this->router->callTarget(); 263 | $this->router->response->sendContent(); 264 | $this->expectOutputString('yes'); 265 | } 266 | 267 | /** 268 | * 269 | */ 270 | public function testApi() 271 | { 272 | $this->router->setUrl('/api/users'); 273 | $this->assertTrue($this->router->match()); 274 | $this->router->callTarget(); 275 | $this->assertEquals(500, $this->router->response->getStatusCode()); 276 | 277 | $this->router->response->setStatusCode(200); 278 | $this->router->setUrl('/api/users/1'); 279 | $this->assertTrue($this->router->match()); 280 | $this->router->callTarget(); 281 | $this->assertEquals('application/json', $this->router->response->headers['Content-Type']); 282 | $this->router->response->sendContent(); 283 | $this->expectOutputString('{"name":"Peter"}'); 284 | } 285 | 286 | /** 287 | * 288 | */ 289 | public function testResponse() 290 | { 291 | $this->router->setUrl('/notfound'); 292 | $this->assertFalse($this->router->match()); 293 | } 294 | 295 | } --------------------------------------------------------------------------------