{"record":{"id":"63ac384f64aa91c8","repo":"phalcon/cphalcon","slug":"forwarding-inside-a-controller-s-initialize-meth","errorCode":null,"errorMessage":"Forwarding inside a controller's initialize() method is forbidden","messagePattern":"Forwarding inside a controller's initialize\\(\\) method is forbidden","errorType":"exception","errorClass":"ForwardInInitializeForbidden","httpStatus":null,"severity":"error","filePath":"phalcon/Dispatcher/AbstractDispatcher.zep","lineNumber":781,"sourceCode":"     *         \"action\"     => \"index\",\n     *     ]\n     * );\n     * ```\n     *\n     * @phpstan-param dispatcher_forward $forward\n     */\n    public function forward(array forward) -> void\n    {\n        var namespaceName, controllerName, params, actionName, taskName;\n\n        if unlikely this->isControllerInitialize === true {\n            /**\n             * Note: Important that we do not throw a \"throwDispatchException\"\n             * call here. This is important because it would allow the\n             * application to break out of the defined logic inside the\n             * dispatcher which handles all dispatch exceptions.\n             */\n            throw new ForwardInInitializeForbidden();\n        }\n\n        /**\n         * Save current values as previous to ensure calls to getPrevious\n         * methods don't return null.\n         */\n        let this->previousNamespaceName = this->namespaceName,\n            this->previousHandlerName = this->handlerName,\n            this->previousActionName = this->actionName;\n\n        // Check if we need to forward to another namespace\n        if fetch namespaceName, forward[\"namespace\"] {\n            let this->namespaceName = namespaceName;\n        }\n\n        // Check if we need to forward to another controller.\n        if fetch controllerName, forward[\"controller\"] {\n            let this->handlerName = controllerName;","sourceCodeStart":763,"sourceCodeEnd":799,"githubUrl":"https://github.com/phalcon/cphalcon/blob/b7419de9cd0a8a3f48441ead84c9f8415d463e25/phalcon/Dispatcher/AbstractDispatcher.zep#L763-L799","documentation":"Phalcon's dispatcher forbids calling forward() while a controller's initialize() method is running. During initialize the dispatch loop has not finished setting up the handler, and a forward there would corrupt the loop state, so AbstractDispatcher::forward() throws ForwardInInitializeForbidden when the internal isControllerInitialize flag is set. The flag is only true for the duration of the initialize() call.","triggerScenarios":"Calling $this->dispatcher->forward([...]) or (new Dispatcher)->forward([...]) from inside a Controller::initialize() method, including indirectly - e.g. initialize() invokes a helper/service that forwards, or an events listener hooked on 'dispatch:beforeInitialize' fires a forward.","commonSituations":"Trying to redirect unauthenticated users to a login action from initialize() instead of a dedicated event; moving logic from beforeExecuteRoute into initialize during refactoring; event handlers (Security plugin, Acl) that forward but were attached to the initialize phase.","solutions":["Move the forward to a dispatch-loop event that legally allows it: attach a listener to 'dispatch:beforeExecuteRoute' (or 'dispatch:beforeDispatch') and call $dispatcher->forward() there.","For auth/ACL gating, put the check in beforeExecuteRoute and forward to a login controller - this is the canonical Phalcon pattern.","If the decision truly belongs to per-controller setup, have initialize() only set state/properties, and let the first line of the target action (or its beforeExecuteRoute) do the forward.","For plain redirects (no controller re-dispatch), use $this->response->redirect() which is not subject to this restriction."],"exampleFix":"// before\nclass UserController extends Controller\n{\n    public function initialize()\n    {\n        if (!$this->session->get('userId')) {\n            $this->dispatcher->forward(['controller' => 'auth', 'action' => 'login']); // throws\n        }\n    }\n}\n\n// after\nclass UserController extends Controller implements EventsInterface // or use a listener\n{\n    public function beforeExecuteRoute(Dispatcher $dispatcher)\n    {\n        if (!$this->session->get('userId')) {\n            $dispatcher->forward(['controller' => 'auth', 'action' => 'login']);\n            return false;\n        }\n    }\n}","handlingStrategy":"validation","validationCode":"// Guard before forwarding when unsure of the current dispatch phase:\n$dispatcher = $this->dispatcher;\n$ref = new \\ReflectionProperty($dispatcher, 'isControllerInitialize');\n$ref->setAccessible(true);\nif ($ref->getValue($dispatcher)) {\n    return; // inside initialize() - do NOT forward here; defer to the action\n}\n$dispatcher->forward($target);","typeGuard":"function canForwardNow(\\Phalcon\\Dispatcher\\DispatcherInterface $dispatcher): bool\n{\n    $ref = new \\ReflectionProperty($dispatcher, 'isControllerInitialize');\n    $ref->setAccessible(true);\n\n    return true !== $ref->getValue($dispatcher);\n}","tryCatchPattern":"try {\n    $this->dispatcher->forward(['controller' => 'auth', 'action' => 'login']);\n} catch (\\Phalcon\\Dispatcher\\Exceptions\\ForwardInInitializeForbidden $e) {\n    // Called during initialize() - fall back to a plain HTTP redirect instead\n    $this->response->redirect('/auth/login')->send();\n}","preventionTips":["Never call forward() from initialize(); reserve it for beforeExecuteRoute/beforeDispatch listeners or actions.","Put auth/ACL gating in a 'dispatch:beforeExecuteRoute' listener - the canonical place for conditional forwards.","Keep initialize() limited to setting controller properties so no lifecycle-order assumptions can break."],"tags":["phalcon","dispatcher","mvc","forward","lifecycle","controllers"],"backgroundTag":"dispatcher-forward-forbidden","analyzedSha":"b7419de9cd0a8a3f48441ead84c9f8415d463e25","analyzedAt":"2026-08-21T06:21:18.811Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}