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