Autoloading Doctrine and Doctrine entities from Zend Framework

A number of people on the mailing list and twitter recently have asked how to autoload Doctrine using Zend Framework's autoloader, as well as how to autoload Doctrine models you've created. Having done a few projects using Doctrine recently, I can actually give an answer.

The short answer: just attach it to Zend_Loader_Autoloader.

Now for the details.

First, make sure the path to the Doctrine.php file is on your include_path.

Next, Zend_Loader_Autoloader allows you to specify "namespaces" (not actual PHP namespaces, more like class prefixes) it can autoload, both for classes it will autoload, as well as for autoload callbacks you attach to it. Typically, you include the trailing underscore when doing so:

$autoloader = Zend_Loader_Autoloader::getInstance();
$autoloader->registerNamespace('Foo_');
$autoloader->pushAutoloader($callback, 'Bar_');

However, because Doctrine has a master class for handling common operations, "Doctrine", we have to omit the trailing underscore so that the Doctrine class itself may be autoloaded. We need to do two different operations: first, add a namespace to Zend_Loader_Autoloader for Doctrine (which will allow us to autoload the Doctrine class itself, as well as the various doctrine subcomponent classes), and then register the Doctrine autoloader (which will be used by Doctrine to load items such as table classes, listeners, etc.):

$autoloader->registerNamespace('Doctrine')
           ->pushAutoloader(array('Doctrine', 'autoload'), 'Doctrine');

This takes care of the Doctrine autoloader; now, let's turn to Doctrine models.

First, tell Doctrine that you want to autoload. You do this by telling it to use "conservative" model loading (shorthand for lazyloading or autoloading), and to autoload table classes:

$manager = Doctrine_Manager::getInstance();
$manager->setAttribute(  
    Doctrine::ATTR_MODEL_LOADING, 
    Doctrine::MODEL_LOADING_CONSERVATIVE
);
$manager->setAttribute(  
    Doctrine::ATTR_AUTOLOAD_TABLE_CLASSES, 
    true
);

From here, you need to ensure you actually can autoload the models. Normally, you tell Doctrine where to find models, but we're in a Zend Framework application, so let's leverage ZF conventions.

I typically put my model code with my application code:

application
|-- Bootstrap.php
|-- configs
|-- controllers
|-- models                 <- HERE
|-- modules
|   `-- blog
|       |-- Bootstrap.php
|       |-- controllers
|       |-- forms
|       |-- models         <- HERE
|       |-- services
|       `-- views
`-- views

Zend Framework already provides mechanisms for autoloading application resources via Zend_Loader_Autoloader_Resource and Zend_Application_Module_Autoloader. Assuming you've extended Zend_Application_Module_Bootstrap in your module bootstraps, you're basically already set. The trick has to do with your table classes; your table classes must be placed in the same directory as your models, and they must be named exactly the same as your models, with the suffix "Table".

For example, if you had the class Blog_Model_Entry extending Doctrine_Record in the file application/modules/blog/models/Entry.php, the related table class would be Blog_Model_EntryTable in the file application/modules/blog/models/EntryTable.php.

I automate most of this setup via my Bootstrap class, which typically looks as follows:

class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
    protected function _initAppAutoload()
    {
        $autoloader = new Zend_Application_Module_Autoloader(array(
            'namespace' => 'App',
            'basePath'  => dirname(__FILE__),
        ));
        return $autoloader;
    }

    protected function _initDoctrine()
    {
        $this->getApplication()->getAutoloader()
                               ->pushAutoloader(array('Doctrine', 'autoload'));

        $manager = Doctrine_Manager::getInstance();
        $manager->setAttribute(Doctrine::ATTR_AUTO_ACCESSOR_OVERRIDE, true);
        $manager->setAttribute(
            Doctrine::ATTR_MODEL_LOADING, 
            Doctrine::MODEL_LOADING_CONSERVATIVE
        );
        $manager->setAttribute(Doctrine::ATTR_AUTOLOAD_TABLE_CLASSES, true);

        $dsn = $this->getOption('dsn');
        $conn = Doctrine_Manager::connection($dsn, 'doctrine');
        $conn->setAttribute(Doctrine::ATTR_USE_NATIVE_ENUM, true);
        return $conn;
    }
}

Within your configuration, you need to add two keys: one for registering the Doctrine namespace with the default autoloader, and another for the dsn:

autoloaderNamespaces[] = "Doctrine"
dsn = "DSN to use with Doctrine goes here"

I also have a script that I use to load all model classes at once in order to do things like generate the schema or test interactions. I'll blog about those at a later date. Hopefully the above information will help one or two of you out there trying to integrate these two codebases!

Updates

  • 2009-08-21: added information about registering Doctrine namespace with default autoloader