1 2/** 3 * This file is part of the Phalcon Framework. 4 * 5 * (c) Phalcon Team <team@phalcon.io> 6 * 7 * For the full copyright and license information, please view the 8 * LICENSE.txt file that was distributed with this source code. 9 */ 10 11namespace Phalcon\Dispatcher; 12 13use Exception; 14use Phalcon\Di\DiInterface; 15use Phalcon\Di\AbstractInjectionAware; 16use Phalcon\Dispatcher\Exception as PhalconException; 17use Phalcon\Events\EventsAwareInterface; 18use Phalcon\Events\ManagerInterface; 19use Phalcon\Filter\FilterInterface; 20use Phalcon\Mvc\Model\Binder; 21use Phalcon\Mvc\Model\BinderInterface; 22 23/** 24 * This is the base class for Phalcon\Mvc\Dispatcher and Phalcon\Cli\Dispatcher. 25 * This class can't be instantiated directly, you can use it to create your own 26 * dispatchers. 27 */ 28abstract class AbstractDispatcher extends AbstractInjectionAware implements DispatcherInterface, EventsAwareInterface 29{ 30 protected activeHandler; 31 32 /** 33 * @var array 34 */ 35 protected activeMethodMap = []; 36 37 protected actionName = null; 38 39 /** 40 * @var string 41 */ 42 protected actionSuffix = "Action"; 43 44 /** 45 * @var array 46 */ 47 protected camelCaseMap = []; 48 49 /** 50 * @var string 51 */ 52 protected defaultAction = ""; 53 54 protected defaultNamespace = null; 55 56 protected defaultHandler = null; 57 58 /** 59 * @var array 60 */ 61 protected handlerHashes = []; 62 63 protected handlerName = null; 64 65 /** 66 * @var string 67 */ 68 protected handlerSuffix = ""; 69 70 protected eventsManager; 71 72 /** 73 * @var bool 74 */ 75 protected finished = false; 76 77 /** 78 * @var bool 79 */ 80 protected forwarded = false; 81 82 /** 83 * @var bool 84 */ 85 protected isControllerInitialize = false; 86 protected lastHandler = null; 87 protected modelBinder = null; 88 89 /** 90 * @var bool 91 */ 92 protected modelBinding = false; 93 protected moduleName = null; 94 protected namespaceName = null; 95 96 /** 97 * @var array 98 */ 99 protected params = []; 100 protected previousActionName = null; 101 protected previousHandlerName = null; 102 protected previousNamespaceName = null; 103 protected returnedValue = null; 104 105 public function callActionMethod(handler, string actionMethod, array! params = []) 106 { 107 return call_user_func_array( 108 [handler, actionMethod], 109 params 110 ); 111 } 112 113 /** 114 * Process the results of the router by calling into the appropriate 115 * controller action(s) including any routing data or injected parameters. 116 * 117 * @return object|false Returns the dispatched handler class (the Controller for Mvc dispatching or a Task 118 * for CLI dispatching) or <tt>false</tt> if an exception occurred and the operation was 119 * stopped by returning <tt>false</tt> in the exception handler. 120 * 121 * @throws \Exception if any uncaught or unhandled exception occurs during the dispatcher process. 122 */ 123 public function dispatch() -> var | bool 124 { 125 bool hasService, hasEventsManager; 126 int numberDispatches; 127 var value, handler, container, namespaceName, handlerName, actionName, 128 params, eventsManager, handlerClass, status, actionMethod, 129 modelBinder, bindCacheKey, isNewHandler, handlerHash, e; 130 131 let container = <DiInterface> this->container; 132 133 if typeof container != "object" { 134 this->{"throwDispatchException"}( 135 PhalconException::containerServiceNotFound( 136 "related dispatching services" 137 ), 138 PhalconException::EXCEPTION_NO_DI 139 ); 140 141 return false; 142 } 143 144 let eventsManager = <ManagerInterface> this->eventsManager; 145 let hasEventsManager = typeof eventsManager == "object"; 146 let this->finished = true; 147 148 if hasEventsManager { 149 try { 150 // Calling beforeDispatchLoop event 151 // Note: Allow user to forward in the beforeDispatchLoop. 152 if eventsManager->fire("dispatch:beforeDispatchLoop", this) === false && this->finished !== false { 153 return false; 154 } 155 } catch Exception, e { 156 // Exception occurred in beforeDispatchLoop. 157 158 /** 159 * The user can optionally forward now in the 160 * `dispatch:beforeException` event or return <tt>false</tt> to 161 * handle the exception and prevent it from bubbling. In the 162 * event the user does forward but does or does not return 163 * false, we assume the forward takes precedence. The returning 164 * false intuitively makes more sense when inside the dispatch 165 * loop and technically we are not here. Therefore, returning 166 * false only impacts whether non-forwarded exceptions are 167 * silently handled or bubbled up the stack. Note that this 168 * behavior is slightly different than other subsequent events 169 * handled inside the dispatch loop. 170 */ 171 172 let status = this->{"handleException"}(e); 173 174 if this->finished !== false { 175 // No forwarding 176 if status === false { 177 return false; 178 } 179 180 // Otherwise, bubble Exception 181 throw e; 182 } 183 184 // Otherwise, user forwarded, continue 185 } 186 } 187 188 let value = null, 189 handler = null, 190 numberDispatches = 0, 191// actionSuffix = this->actionSuffix, // never used 192 this->finished = false; 193 194 while !this->finished { 195 let numberDispatches++; 196 197 // Throw an exception after 256 consecutive forwards 198 if unlikely numberDispatches == 256 { 199 this->{"throwDispatchException"}( 200 "Dispatcher has detected a cyclic routing causing stability problems", 201 PhalconException::EXCEPTION_CYCLIC_ROUTING 202 ); 203 204 break; 205 } 206 207 let this->finished = true; 208 209 this->resolveEmptyProperties(); 210 211 if hasEventsManager { 212 try { 213 // Calling "dispatch:beforeDispatch" event 214 if eventsManager->fire("dispatch:beforeDispatch", this) === false || this->finished === false { 215 continue; 216 } 217 } catch Exception, e { 218 if this->{"handleException"}(e) === false || this->finished === false { 219 continue; 220 } 221 222 throw e; 223 } 224 } 225 226 let handlerClass = this->getHandlerClass(); 227 228 /** 229 * Handlers are retrieved as shared instances from the Service 230 * Container 231 */ 232 let hasService = (bool) container->has(handlerClass); 233 234 if !hasService { 235 /** 236 * DI doesn't have a service with that name, try to load it 237 * using an autoloader 238 */ 239 let hasService = (bool) class_exists(handlerClass); 240 } 241 242 // If the service can be loaded we throw an exception 243 if !hasService { 244 let status = this->{"throwDispatchException"}( 245 handlerClass . " handler class cannot be loaded", 246 PhalconException::EXCEPTION_HANDLER_NOT_FOUND 247 ); 248 249 if status === false && this->finished === false { 250 continue; 251 } 252 253 break; 254 } 255 256 let handler = container->getShared(handlerClass); 257 258 // Handlers must be only objects 259 if unlikely typeof handler !== "object" { 260 let status = this->{"throwDispatchException"}( 261 "Invalid handler returned from the services container", 262 PhalconException::EXCEPTION_INVALID_HANDLER 263 ); 264 265 if status === false && this->finished === false { 266 continue; 267 } 268 269 break; 270 } 271 272 // Check if the handler is new (hasn't been initialized). 273 let handlerHash = spl_object_hash(handler); 274 275 let isNewHandler = !(isset this->handlerHashes[handlerHash]); 276 277 if isNewHandler { 278 let this->handlerHashes[handlerHash] = true; 279 } 280 281 let this->activeHandler = handler; 282 283 let namespaceName = this->namespaceName; 284 let handlerName = this->handlerName; 285 let actionName = this->actionName; 286 let params = this->params; 287 288 /** 289 * Check if the params is an array 290 */ 291 if unlikely typeof params != "array" { 292 /** 293 * An invalid parameter variable was passed throw an exception 294 */ 295 let status = this->{"throwDispatchException"}( 296 "Action parameters must be an Array", 297 PhalconException::EXCEPTION_INVALID_PARAMS 298 ); 299 300 if status === false && this->finished === false { 301 continue; 302 } 303 304 break; 305 } 306 307 // Check if the method exists in the handler 308 let actionMethod = this->getActiveMethod(); 309 310 if unlikely !is_callable([handler, actionMethod]) { 311 if hasEventsManager { 312 if eventsManager->fire("dispatch:beforeNotFoundAction", this) === false { 313 continue; 314 } 315 316 if this->finished === false { 317 continue; 318 } 319 } 320 321 /** 322 * Try to throw an exception when an action isn't defined on the 323 * object 324 */ 325 let status = this->{"throwDispatchException"}( 326 "Action '" . actionName . "' was not found on handler '" . handlerName . "'", 327 PhalconException::EXCEPTION_ACTION_NOT_FOUND 328 ); 329 330 if status === false && this->finished === false { 331 continue; 332 } 333 334 break; 335 } 336 337 /** 338 * In order to ensure that the `initialize()` gets called we'll 339 * destroy the current handlerClass from the DI container in the 340 * event that an error occurs and we continue out of this block. 341 * This is necessary because there is a disjoin between retrieval of 342 * the instance and the execution of the `initialize()` event. From 343 * a coding perspective, it would have made more sense to probably 344 * put the `initialize()` prior to the beforeExecuteRoute which 345 * would have solved this. However, for posterity, and to remain 346 * consistency, we'll ensure the default and documented behavior 347 * works correctly. 348 */ 349 if hasEventsManager { 350 try { 351 // Calling "dispatch:beforeExecuteRoute" event 352 if eventsManager->fire("dispatch:beforeExecuteRoute", this) === false || this->finished === false { 353 container->remove(handlerClass); 354 continue; 355 } 356 } catch Exception, e { 357 if this->{"handleException"}(e) === false || this->finished === false { 358 container->remove(handlerClass); 359 360 continue; 361 } 362 363 throw e; 364 } 365 } 366 367 if method_exists(handler, "beforeExecuteRoute") { 368 try { 369 // Calling "beforeExecuteRoute" as direct method 370 if handler->beforeExecuteRoute(this) === false || this->finished === false { 371 container->remove(handlerClass); 372 373 continue; 374 } 375 } catch Exception, e { 376 if this->{"handleException"}(e) === false || this->finished === false { 377 container->remove(handlerClass); 378 379 continue; 380 } 381 382 throw e; 383 } 384 } 385 386 /** 387 * Call the "initialize" method just once per request 388 * 389 * Note: The `dispatch:afterInitialize` event is called regardless 390 * of the presence of an `initialize()` method. The naming is 391 * poor; however, the intent is for a more global "constructor 392 * is ready to go" or similarly "__onConstruct()" methodology. 393 * 394 * Note: In Phalcon 4.0, the `initialize()` and 395 * `dispatch:afterInitialize` event will be handled prior to the 396 * `beforeExecuteRoute` event/method blocks. This was a bug in the 397 * original design that was not able to change due to widespread 398 * implementation. With proper documentation change and blog posts 399 * for 4.0, this change will happen. 400 * 401 * @see https://github.com/phalcon/cphalcon/pull/13112 402 */ 403 if isNewHandler { 404 if method_exists(handler, "initialize") { 405 try { 406 let this->isControllerInitialize = true; 407 408 handler->initialize(); 409 } catch Exception, e { 410 let this->isControllerInitialize = false; 411 412 /** 413 * If this is a dispatch exception (e.g. From 414 * forwarding) ensure we don't handle this twice. In 415 * order to ensure this doesn't happen all other 416 * exceptions thrown outside this method in this class 417 * should not call "throwDispatchException" but instead 418 * throw a normal Exception. 419 */ 420 if this->{"handleException"}(e) === false || this->finished === false { 421 continue; 422 } 423 424 throw e; 425 } 426 } 427 428 let this->isControllerInitialize = false; 429 430 /** 431 * Calling "dispatch:afterInitialize" event 432 */ 433 if eventsManager { 434 try { 435 if eventsManager->fire("dispatch:afterInitialize", this) === false || this->finished === false { 436 continue; 437 } 438 } catch Exception, e { 439 if this->{"handleException"}(e) === false || this->finished === false { 440 continue; 441 } 442 443 throw e; 444 } 445 } 446 } 447 448 if this->modelBinding { 449 let modelBinder = this->modelBinder; 450 let bindCacheKey = "_PHMB_" . handlerClass . "_" . actionMethod; 451 452 let params = modelBinder->bindToHandler( 453 handler, 454 params, 455 bindCacheKey, 456 actionMethod 457 ); 458 } 459 460 /** 461 * Calling afterBinding 462 */ 463 if hasEventsManager { 464 if eventsManager->fire("dispatch:afterBinding", this) === false { 465 continue; 466 } 467 468 /** 469 * Check if the user made a forward in the listener 470 */ 471 if this->finished === false { 472 continue; 473 } 474 } 475 476 /** 477 * Calling afterBinding as callback and event 478 */ 479 if method_exists(handler, "afterBinding") { 480 if handler->afterBinding(this) === false { 481 continue; 482 } 483 484 /** 485 * Check if the user made a forward in the listener 486 */ 487 if this->finished === false { 488 continue; 489 } 490 } 491 492 /** 493 * Save the current handler 494 */ 495 let this->lastHandler = handler; 496 497 try { 498 /** 499 * We update the latest value produced by the latest handler 500 */ 501 let this->returnedValue = this->callActionMethod( 502 handler, 503 actionMethod, 504 params 505 ); 506 507 if this->finished === false { 508 continue; 509 } 510 } catch Exception, e { 511 if this->{"handleException"}(e) === false || this->finished === false { 512 continue; 513 } 514 515 throw e; 516 } 517 518 /** 519 * Calling "dispatch:afterExecuteRoute" event 520 */ 521 if hasEventsManager { 522 try { 523 if eventsManager->fire("dispatch:afterExecuteRoute", this, value) === false || this->finished === false { 524 continue; 525 } 526 } catch Exception, e { 527 if this->{"handleException"}(e) === false || this->finished === false { 528 continue; 529 } 530 531 throw e; 532 } 533 } 534 535 /** 536 * Calling "afterExecuteRoute" as direct method 537 */ 538 if method_exists(handler, "afterExecuteRoute") { 539 try { 540 if handler->afterExecuteRoute(this, value) === false || this->finished === false { 541 continue; 542 } 543 } catch Exception, e { 544 if this->{"handleException"}(e) === false || this->finished === false { 545 continue; 546 } 547 548 throw e; 549 } 550 } 551 552 // Calling "dispatch:afterDispatch" event 553 if hasEventsManager { 554 try { 555 eventsManager->fire("dispatch:afterDispatch", this, value); 556 } catch Exception, e { 557 /** 558 * Still check for finished here as we want to prioritize 559 * `forwarding()` calls 560 */ 561 if this->{"handleException"}(e) === false || this->finished === false { 562 continue; 563 } 564 565 throw e; 566 } 567 } 568 } 569 570 if hasEventsManager { 571 try { 572 // Calling "dispatch:afterDispatchLoop" event 573 // Note: We don't worry about forwarding in after dispatch loop. 574 eventsManager->fire("dispatch:afterDispatchLoop", this); 575 } catch Exception, e { 576 // Exception occurred in afterDispatchLoop. 577 if this->{"handleException"}(e) === false { 578 return false; 579 } 580 581 // Otherwise, bubble Exception 582 throw e; 583 } 584 } 585 586 return handler; 587 } 588 589 /** 590 * Forwards the execution flow to another controller/action. 591 * 592 * ```php 593 * $this->dispatcher->forward( 594 * [ 595 * "controller" => "posts", 596 * "action" => "index", 597 * ] 598 * ); 599 * ``` 600 * 601 * @throws \Phalcon\Exception 602 */ 603 public function forward(array forward) -> void 604 { 605 var namespaceName, controllerName, params, actionName, taskName; 606 607 if unlikely this->isControllerInitialize === true { 608 /** 609 * Note: Important that we do not throw a "throwDispatchException" 610 * call here. This is important because it would allow the 611 * application to break out of the defined logic inside the 612 * dispatcher which handles all dispatch exceptions. 613 */ 614 throw new PhalconException( 615 "Forwarding inside a controller's initialize() method is forbidden" 616 ); 617 } 618 619 /** 620 * Save current values as previous to ensure calls to getPrevious 621 * methods don't return null. 622 */ 623 let this->previousNamespaceName = this->namespaceName, 624 this->previousHandlerName = this->handlerName, 625 this->previousActionName = this->actionName; 626 627 // Check if we need to forward to another namespace 628 if fetch namespaceName, forward["namespace"] { 629 let this->namespaceName = namespaceName; 630 } 631 632 // Check if we need to forward to another controller. 633 if fetch controllerName, forward["controller"] { 634 let this->handlerName = controllerName; 635 } elseif fetch taskName, forward["task"] { 636 let this->handlerName = taskName; 637 } 638 639 // Check if we need to forward to another action 640 if fetch actionName, forward["action"] { 641 let this->actionName = actionName; 642 } 643 644 // Check if we need to forward changing the current parameters 645 if fetch params, forward["params"] { 646 let this->params = params; 647 } 648 649 let this->finished = false, 650 this->forwarded = true; 651 } 652 653 /** 654 * Gets the latest dispatched action name 655 */ 656 public function getActionName() -> string 657 { 658 return this->actionName; 659 } 660 661 /** 662 * Gets the default action suffix 663 */ 664 public function getActionSuffix() -> string 665 { 666 return this->actionSuffix; 667 } 668 669 /** 670 * Returns the current method to be/executed in the dispatcher 671 */ 672 public function getActiveMethod() -> string 673 { 674 var activeMethodName; 675 676 if !fetch activeMethodName, this->activeMethodMap[this->actionName] { 677 let activeMethodName = lcfirst( 678 this->toCamelCase( 679 this->actionName 680 ) 681 ); 682 683 let this->activeMethodMap[this->actionName] = activeMethodName; 684 } 685 686 return activeMethodName . this->actionSuffix; 687 } 688 689 /** 690 * Returns bound models from binder instance 691 * 692 * ```php 693 * class UserController extends Controller 694 * { 695 * public function showAction(User $user) 696 * { 697 * // return array with $user 698 * $boundModels = $this->dispatcher->getBoundModels(); 699 * } 700 * } 701 * ``` 702 */ 703 public function getBoundModels() -> array 704 { 705 var modelBinder; 706 707 let modelBinder = this->modelBinder; 708 709 if modelBinder == null { 710 return []; 711 } 712 713 return modelBinder->getBoundModels(); 714 } 715 716 /** 717 * Returns the default namespace 718 */ 719 public function getDefaultNamespace() -> string 720 { 721 return this->defaultNamespace; 722 } 723 724 /** 725 * Returns the internal event manager 726 */ 727 public function getEventsManager() -> <ManagerInterface> 728 { 729 return this->eventsManager; 730 } 731 732 /** 733 * Gets the default handler suffix 734 */ 735 public function getHandlerSuffix() -> string 736 { 737 return this->handlerSuffix; 738 } 739 740 /** 741 * Gets model binder 742 */ 743 public function getModelBinder() -> <BinderInterface> | null 744 { 745 return this->modelBinder; 746 } 747 748 /** 749 * Gets the module where the controller class is 750 */ 751 public function getModuleName() -> string 752 { 753 return this->moduleName; 754 } 755 756 /** 757 * Gets a namespace to be prepended to the current handler name 758 */ 759 public function getNamespaceName() -> string 760 { 761 return this->namespaceName; 762 } 763 764 /** 765 * Gets a param by its name or numeric index 766 * 767 * @param mixed param 768 * @param string|array filters 769 * @param mixed defaultValue 770 * @return mixed 771 */ 772 public function getParam(var param, filters = null, defaultValue = null) -> var 773 { 774 var params, filter, paramValue, container; 775 776 let params = this->params; 777 778 if !fetch paramValue, params[param] { 779 return defaultValue; 780 } 781 782 if filters === null { 783 return paramValue; 784 } 785 786 let container = this->container; 787 788 if typeof container != "object" { 789 this->{"throwDispatchException"}( 790 PhalconException::containerServiceNotFound( 791 "the 'filter' service" 792 ), 793 PhalconException::EXCEPTION_NO_DI 794 ); 795 } 796 797 let filter = <FilterInterface> container->getShared("filter"); 798 799 return filter->sanitize(paramValue, filters); 800 } 801 802 /** 803 * Gets action params 804 */ 805 public function getParams() -> array 806 { 807 return this->params; 808 } 809 810 /** 811 * Check if a param exists 812 */ 813 public function hasParam(var param) -> bool 814 { 815 return isset this->params[param]; 816 } 817 818 /** 819 * Checks if the dispatch loop is finished or has more pendent 820 * controllers/tasks to dispatch 821 */ 822 public function isFinished() -> bool 823 { 824 return this->finished; 825 } 826 827 /** 828 * Sets the action name to be dispatched 829 */ 830 public function setActionName(string actionName) -> void 831 { 832 let this->actionName = actionName; 833 } 834 835 836 /** 837 * Sets the default action name 838 */ 839 public function setDefaultAction(string actionName) -> void 840 { 841 let this->defaultAction = actionName; 842 } 843 844 /** 845 * Sets the default namespace 846 */ 847 public function setDefaultNamespace(string namespaceName) -> void 848 { 849 let this->defaultNamespace = namespaceName; 850 } 851 852 /** 853 * Possible class name that will be located to dispatch the request 854 */ 855 public function getHandlerClass() -> string 856 { 857 var handlerSuffix, handlerName, namespaceName, camelizedClass, 858 handlerClass; 859 860 this->resolveEmptyProperties(); 861 862 let handlerSuffix = this->handlerSuffix, 863 handlerName = this->handlerName, 864 namespaceName = this->namespaceName; 865 866 // We don't camelize the classes if they are in namespaces 867 if !memstr(handlerName, "\\") { 868 let camelizedClass = this->toCamelCase(handlerName); 869 } else { 870 let camelizedClass = handlerName; 871 } 872 873 // Create the complete controller class name prepending the namespace 874 if namespaceName { 875 if !ends_with(namespaceName, "\\") { 876 let namespaceName .= "\\"; 877 } 878 879 let handlerClass = namespaceName . camelizedClass . handlerSuffix; 880 } else { 881 let handlerClass = camelizedClass . handlerSuffix; 882 } 883 884 return handlerClass; 885 } 886 887 /** 888 * Set a param by its name or numeric index 889 */ 890 public function setParam(var param, var value) -> void 891 { 892 let this->params[param] = value; 893 } 894 895 /** 896 * Sets action params to be dispatched 897 */ 898 public function setParams(array params) -> void 899 { 900 let this->params = params; 901 } 902 903 /** 904 * Sets the latest returned value by an action manually 905 */ 906 public function setReturnedValue(var value) -> void 907 { 908 let this->returnedValue = value; 909 } 910 911 /** 912 * Sets the default action suffix 913 */ 914 public function setActionSuffix(string actionSuffix) -> void 915 { 916 let this->actionSuffix = actionSuffix; 917 } 918 919 /** 920 * Sets the events manager 921 */ 922 public function setEventsManager(<ManagerInterface> eventsManager) -> void 923 { 924 let this->eventsManager = eventsManager; 925 } 926 927 /** 928 * Sets the default suffix for the handler 929 */ 930 public function setHandlerSuffix(string handlerSuffix) -> void 931 { 932 let this->handlerSuffix = handlerSuffix; 933 } 934 935 /** 936 * Enable model binding during dispatch 937 * 938 * ```php 939 * $di->set( 940 * 'dispatcher', 941 * function() { 942 * $dispatcher = new Dispatcher(); 943 * 944 * $dispatcher->setModelBinder( 945 * new Binder(), 946 * 'cache' 947 * ); 948 * 949 * return $dispatcher; 950 * } 951 * ); 952 * ``` 953 */ 954 public function setModelBinder(<BinderInterface> modelBinder, var cache = null) -> <DispatcherInterface> 955 { 956 var container; 957 958 if typeof cache == "string" { 959 let container = this->container; 960 961 let cache = container->get(cache); 962 } 963 964 if cache != null { 965 modelBinder->setCache(cache); 966 } 967 968 let this->modelBinding = true; 969 let this->modelBinder = modelBinder; 970 971 return this; 972 } 973 974 /** 975 * Sets the module where the controller is (only informative) 976 */ 977 public function setModuleName(string moduleName) -> void 978 { 979 let this->moduleName = moduleName; 980 } 981 982 /** 983 * Sets the namespace where the controller class is 984 */ 985 public function setNamespaceName(string namespaceName) -> void 986 { 987 let this->namespaceName = namespaceName; 988 } 989 990 /** 991 * Returns value returned by the latest dispatched action 992 */ 993 public function getReturnedValue() -> var 994 { 995 return this->returnedValue; 996 } 997 998 /** 999 * Check if the current executed action was forwarded by another one 1000 */ 1001 public function wasForwarded() -> bool 1002 { 1003 return this->forwarded; 1004 } 1005 1006 /** 1007 * Set empty properties to their defaults (where defaults are available) 1008 */ 1009 protected function resolveEmptyProperties() -> void 1010 { 1011 // If the current namespace is null we use the default namespace 1012 if !this->namespaceName { 1013 let this->namespaceName = this->defaultNamespace; 1014 } 1015 1016 // If the handler is null we use the default handler 1017 if !this->handlerName { 1018 let this->handlerName = this->defaultHandler; 1019 } 1020 1021 // If the action is null we use the default action 1022 if !this->actionName { 1023 let this->actionName = this->defaultAction; 1024 } 1025 } 1026 1027 protected function toCamelCase(string input) -> string 1028 { 1029 var camelCaseInput; 1030 1031 if !fetch camelCaseInput, this->camelCaseMap[input] { 1032 let camelCaseInput = join( 1033 "", 1034 array_map( 1035 "ucfirst", 1036 preg_split("/[_-]+/", input) 1037 ) 1038 ); 1039 1040 let this->camelCaseMap[input] = camelCaseInput; 1041 } 1042 1043 return camelCaseInput; 1044 } 1045} 1046