Doctrine modular loading of models

Nowadays a lot of effort is made to have support for Doctrine in the Zend Framework. Doctrine is a great tool and having the support for all Doctrine specialities (code generations, migrations and behaviours like i18n and versionable) is a must for all real developers. Searching the Internet about a variety of topics, I think I found now the perfect method to have Doctrine loaded all your models.

The problem

Doctrine doesn't know anything about the modular Zend Framework structure. Therefore, Doctrine needs to be told where the models are, otherwise it's impossible to load them automatically.

The method

During bootstrap several resources are called to configure your application. For modular applications, you already use the modules plugin resource. The modules resource makes it possible to load several php classes (controllers, models, view scripts etc). The resource depends on the bootstrap classes inside the modules.

We're going to use this module bootstrap class to tell Doctrine we have some models to load. Also, an additional Doctrine init method is used to configure some global, application wide settings.

The solution

The Doctrine init method is just the same as Matthew has blogged about, so I'm not going to explain that in detail. Copy that into your own bootstrap class.

As an example, I'll load the models from the blog module. Therefore, inside application/modules/blog/ there is a bootstrap file Bootstrap.php with a Blog_Bootstrap class. Usually this class extends Zend_Application_Module_Bootstrap. Because we're adding functionality to the module bootstrap class, we change the parent class to My_Application_Module_Bootstrap. Now create the file (in library/My/Application/Module/Bootstrap.php) and make sure the class My_Application_Module_Bootstrap is extending the Zend_Application_Module_Bootstrap class:

<?php
class My_Application_Module_Bootstrap extends Zend_Application_Module_Bootstrap
{
}

This class will be added with functionality to load the Doctrine models. Therefore, we add a constructor to call an init method:

public function __construct($application)
{
  parent::__construct($application);
  $this->init();
}

Now the constructor works as normal (it executes Zend_Application_Module_Bootstrap::__construct()) and calls an additional init method. Of course we'll add that too:

public function init()
{
  $this->getPluginLoader()->addPrefixPath(
    'My_Application_Module_Resource',
    'My/Application/Module/Resource'
  );
  $this->registerPluginResource(new My_Application_Module_Resource_Doctrine)
        ->bootstrap('doctrine');
}

This method will add a new folder where Zend can find bootstrap resources. Then we're adding a new resource and execute the resource immediately. The resource is called Doctrine so we know what it's about.

The last part is to create the doctrine resource. Create the file library/My/Application/Module/Resource/Doctrine.php and put the following inside that file:

<?php
class My_Application_Module_Resource_Doctrine
  extends Zend_Application_Resource_ResourceAbstract
{
    public function init()
    {
        $this->_loadModels();
    }

    protected function _loadModels()
    {
        $bootstrap = $this->getBootstrap();
        if (!($bootstrap instanceof Zend_Application_Module_Bootstrap)) {
            throw new Zend_Application_Exception('Invalid bootstrap class');
        }

        $module = strtolower($bootstrap->getModuleName());
        $path   = $bootstrap->getApplication()
                            ->bootstrap('frontController')
                            ->getResource('frontController')
                            ->getModuleDirectory($module);
        $path   = $path . DIRECTORY_SEPARATOR . 'models' . DIRECTORY_SEPARATOR;

        if (is_dir($path)) {
            Doctrine_Core::loadModels($path);
        }
    }
}

It's the usual init() and, for good practice, it calls another method. The method gets the current module and create a path to that module directory (for our blog it's now application/modules/blog). We assume all the models are in the models directory, so add that to the path. To be sure, check if the path is pointing to a directory. If it's the case, let Doctrine load the models.

Conclusion

If all your module bootstrap classes are extending My_Application_Module_Bootstrap it's all done. The bootstrap class looks for a models directory inside the module directory. If it's there, pass it through to Doctrine. That should be all and Doctrine can find your models :)