13

I have some code in the checkout where I set a key in the session if that key is set to false anywhere in the checkout I need to send them back to the billing page. I have the code for it, but I also can't have any of the code that is typically ran after the observer because it will call a third party service and come back as wrong because of this key that is missing in the session

Here is my code, I have everything I want but i need the response to happen immediatly and for nothing after the dispatched event line to be fired only the response sent back to the browser.

public function checkForOrdKey(Varien_Event_Observer $observer)
    {
        $controllerAction = $observer->getControllerAction();
        $request = $controllerAction->getRequest();
        $controllerName = $request->getControllerName();
        $stepData = $this->_getCheckoutSession()->getStepData();
        $ordKeyRemoved = $this->_getCheckoutSession()->getOrdKeyRemoved();
        // if it is the checkout onepage controller or inventory controller don't do anything
        if (isset($controllerName) && $controllerName === "onepage" && $stepData['shipping']['complete'] && $ordKeyRemoved) {
            $this->_getCheckoutSession()->setStepData('shipping', 'complete', false);
            $result['goto_section'] = 'billing';
            Mage::app()->getResponse()->setBody(Mage::helper('core')->jsonEncode($result));
            $this->_getCheckoutSession()->setOrdKeyRemoved(false);

        }
    }
dan.codes
  • 3,523
  • 9
  • 45
  • 59

1 Answers1

27

Basically you need to take control of the creation and sending of the Response object. The normal flow of the controller will process all the method's inline logic, fire it's Events and collect additions to the Response along the way, then the Magento framework will finalize and send the Response.

You can short-circuit that flow in the Observer by attaching to the preDispatch event (controller_action_predispatch_checkout_onepage_savebilling) and then executing this:

$request = Mage::app()->getRequest();
$action = $request->getActionName();
Mage::app()->getFrontController()->getAction()->setFlag($action, Mage_Core_Controller_Varien_Action::FLAG_NO_DISPATCH, true);

The lines above instruct Mage_Core_Controller_Varien_Action (grandparent of all controllers) to bypass the action that has been called (review line 414 in CE 1.4.2 to see how this works). Then proceed with creating your own response and sending it back to the browser. You will need to investigate the correct JSON format to have to the checkout JS classes render any error messages, but something along these lines...

$response = Mage::app()->getResponse();
$response->setHttpResponseCode(500);  //adjust to be whatever code is relevant
$json = Mage::helper('core')->jsonEncode($this->__('Your message here'));  //adjust
$response->setBody($json);
//don't need to sendResponse() as the framework will do this later

That way you're working within the Zend/Magento framework and you don't need to Override the CheckoutController (please, never ever...) or use "exit/die()" hackiness. The reason that exit/die is bad is that it prevents any later Observers that have registered an interest in that Event being able to act. It would be extremely frustrating as a developer to register an Observer that never gets called because another developer has exit'd before you get hit!!

Note that setting the no-dispatch flag will only work if you are hooked into the predispatch Event.

For further info, review the Magento sequence diagram to see how you are bypassing the Layout/Block/Template sections of the flow.

Jonathan Day
  • 18,519
  • 10
  • 84
  • 137
  • hmm it still seems to be trying to execute the rest of the code even after sending the response. – dan.codes Mar 14 '11 at 01:02
  • It did work after putting exit() on it, why is it so bad to do this? – dan.codes Mar 14 '11 at 18:04
  • I have the same kind of requirement as you, dan. Like Jonathan and Nick mentioned, I am hesistant to put an "exit()" in there. Don't know why exactly, I have always avoided exit() except in the simplest of scripts. I would really like to know if there are any side effects of issuing an exit() in a predispatch event. – shaune Mar 14 '11 at 18:29
  • @sdek and @dan.codes, see updated answer with better option and rationale why to avoid exit – Jonathan Day Mar 14 '11 at 22:41
  • 2
    Great answer man. However after testing it we might want to update your answer to not send the response, as long as you set the response body that will get sent at the end, otherwise it will send the same response twice and that will mess everything up. This works great man, thanks for doing the research I owe you a ton. Once again, I have increased my Mage knowledge a ton by asking a question. This one is going on my favs. – dan.codes Mar 15 '11 at 01:51
  • thanks for the feedback @dan, i'll take out the sendResponse(). Pleased that it worked for you. I had to solve a similar issue not long ago, just had to dig it out of the memory banks! :) – Jonathan Day Mar 15 '11 at 01:58
  • agreed... very nice answer Jonathan. Thanks for taking the time to share. – shaune Mar 15 '11 at 14:33
  • 1
    Update: Although I agree setting the no-dispatch flag is the "best" option, there may be SOME cases where the "right" option is just to exit(). I tried out Jonathan's code, which is superb, but it exposed the fact that I have a few other observers that run right afterward that I DON"T WANT TO RUN in the case that this observer function returns output (basically returning an error message). Jonathan's code is the best answer here for 95%+ of all scenarios, but I guess the moral of the story is that you just really have to know your code to know if it is the right answer for your situation. – shaune Mar 15 '11 at 14:56
  • @shaune If these other observers which you do not want to run are written by your own extension then it would be very easy to set a session value to indicate to them to not execute. Rather than using exit(); or die(); – Darren Felton Mar 09 '16 at 17:22