ZF-12350: Function preDispatch called twice in case of module specific bootstrap

Description

This bug occures while using modular ZF1 structure and module specific Bootstrap class. To reproduce this bug:

  1. Create empty ZF1 project (I was using ZF Tool) and make it default modular structure.
  2. Add following settings into application.ini:
resources.frontController.moduleDirectory   = APPLICATION_PATH "/modules"
resources.modules[] =
  1. Add to default module empty boostrap class:
<?php

class Default_Bootstrap extends Zend_Application_Bootstrap_Bootstrap {
}
  1. Create any FrontController Plugin with preDispatch method and add it using application.ini for e.g.:
resources.frontController.plugins.dispatchTest = "Test_Controller_Plugin_DispatchTest"

From now on preDispatch method will be called more than once. This leads to confusion.

To fix this behaviour it's needed to remove module specific Bootstrap.

Expected behaviour:

PreDispatch called only once.

Comments

This sounds more like the common problem of not having a favicon.ico, or another missing file which is referenced in the output html (like a link to a stylesheet which is broken).

Could you clarify how you are determining that preDispatch is called twice?

Please also note, that preDispatch will also be called more than once on requests involving the error controller, or any use of _forward in a controller, or if you change the requested controller / action, as all of these methods involve running a second iteration of the dispatch loop.

I'm going to close this issue as preDispatch being called more than once is expected behaviour in many circumstances.

Please provide a clear, reproduce case, and documentation of how you are observing preDispatch being called more than once if you wish to re-open, so that we can rule out all of the legitimate use cases where this is normal behaviour.

I tried this while using curl instead of a browser (so this shouldn't make favicon.ico request). Additionally it's like I wrote: when you remove module specific Bootstrap, everything behaves like it should. There is no _forward call, there is no error either.

I will write again how to reproduce this bug:

  1. Create new empty ZF project (using ZF tool) in zf1-project directory
  2. cd zf1-project
  3. mkdir application/modules/default
  4. mv application/controllers application/modules/default
  5. mv application/views application/modules/default
  6. Create test plugin:
<?php

class Test_Controller_Plugin_Test extends Zend_Controller_Plugin_Abstract {

    public function preDispatch(Zend_Controller_Request_Abstract $request) {
        var_dump('pre dispatch');
    }
}

and put it into file 'library/Test/Controller/Plugin/Test.php'

  1. Add following settings into application.ini:
autoloaderNamespaces.Test = "Test_"
resources.frontController.moduleDirectory   = APPLICATION_PATH "/modules"
resources.modules[] =
resources.frontController.plugins.test = "Test_Controller_Plugin_Test"

  1. Create Bootstrap class for default module:
<?php

class Default_Bootstrap extends Zend_Application_Bootstrap_Bootstrap {
}

and put this code into 'application/modules/default/Bootstrap.php'

  1. Open a browser or use cURL to test (for e.g. localhost/zf1-project/public) the text 'pre dispatch' will be seen twice.

Additionally you can change Plugin class to:

<?php

class Test_Controller_Plugin_Test extends Zend_Controller_Plugin_Abstract {

    private static $_preDispatchCalled = 0;

    public function preDispatch(Zend_Controller_Request_Abstract $request) {
        self::$_preDispatchCalled++;
    }

    public static function getDispatchCalled() {
        return self::$_preDispatchCalled;
    }
}

And run a test:

<?php

class IndexControllerTest extends Zend_Test_PHPUnit_ControllerTestCase
{

    public function setUp()
    {
        $this->bootstrap = new Zend_Application(APPLICATION_ENV, APPLICATION_PATH . '/configs/application.ini');
        parent::setUp();
    }

    public function testPreDispatchOnce() {
        $this->dispatch('/');
        $this->assertTrue(Test_Controller_Plugin_Test::getDispatchCalled() === 1);
    }


}

This test will fail whether it shoulnd't have

Given the steps you outlined, there are no controllers in your new 'default' module, so there would have been a controller not found exception, looping the request to the error controller. try creating application/modules/default/controllers/IndexController.php and in it put:


<?php

class IndexController extends Zend_Controller_Action
{
    public function indexAction()
    {
        $this->getHelper('viewRenderer')->setNeverRender(true);
    }
}

This should make your test pass (it does for me). Also, this is expected behaviour.

Additionally, I've just noticed I did one thing different from you when creating the test, I used Zend_Application_Module_Bootstap as the bootstrap for the 'default' module as it is a module bootstrap the way you have used it. Having two or more Zend_Application_Bootstrap_Bootstrap bootstraps will cause unexpected problems also, as this is not its intended use.

You would save yourself some pain by not having a 'default' module in the modules directory, as this really isn't the way that your application will behave anyway.

Yes, thank you. probably that was the cause. Sorry for the problem.